User:Kimm Paulino/Scripts

From Second Life Wiki
< User:Kimm Paulino
Revision as of 15:08, 15 January 2012 by Kimm Paulino (talk | contribs) (Added dialog texture changer (and fixed some formatting))
Jump to navigation Jump to search

Simple Teleport

<lsl> // Teleports on 'left click' (touch). // // Set the coordinates to tp to in the DEST= line after these comments. // // Kimm Paulino // Sept 2009 // // Teleport bit based on the following: // // Copyright (c) 2008, Scripting Your World // All rights reserved. // // Scripting Your World // By Dana Moore, Michael Thome, and Dr. Karen Zita Haigh // http://syw.fabulo.us // http://www.amazon.com/Scripting-Your-World-Official-Second/dp/0470339837/ // // You are permitted to use, share, and adapt this code under the // terms of the Creative Commons Public License described in full // at http://creativecommons.org/licenses/by/3.0/legalcode. // That means you must keep the credits, do nothing to damage our // reputation, and do not suggest that we endorse you or your work. // // Listing 2.4: Teleport3 - Optimized Intrasim Teleport

vector DEST = <224, 213, 22>; // the teleport will be to this location vector SITPOS = <0,0,0.5>; key gAv;

moveTo(vector origin, vector destination ) { // removed jumpdist

   float dist = llVecDist(origin, destination);
   integer passes = llCeil( llLog(dist/10.0) / llLog(2.0) );
   integer i;
   list params = [PRIM_POSITION, destination];
   for (i=0; i<passes; i++) {
       params = (params=[]) + params + params;
   }
   llSetPrimitiveParams(params);

}

teleport(key av) {

   if (av == NULL_KEY)
   {
       return;
   }
   vector origin = llGetPos();
   llSetAlpha (0.0, ALL_SIDES);
   moveTo(origin, DEST);
   // no need to sleep -- llSetPrimParams has 0.2s delay
   llUnSit(av);
   moveTo(DEST, origin);
   llSetAlpha (1.0, ALL_SIDES);

}

default {

   state_entry()
   {
       llSetClickAction (CLICK_ACTION_SIT);
       llSitTarget(SITPOS, ZERO_ROTATION);
   }
   changed(integer changebits)
   {
       if (changebits & CHANGED_LINK)
       {
           gAv = llAvatarOnSitTarget();
           if (gAv != NULL_KEY)
           {
               teleport(gAv);
           }
       }
   }

} </lsl>

Die after 10 minutes

<lsl> // This will kill the prim it is in, 10 minutes after the prim has been rezzed. // // Kimm Paulino, June 2010

default {

   state_entry()
   {
       // 10 minutes
       llSetTimerEvent (600.0);
   }
   
   timer ()
   {
       llSetTimerEvent (0.0);
       llDie();
   }

} </lsl>


Simple Prim Texture Changer

<lsl> // Texture changer. Will cycle through all textures // in the prims inventory. // // Kimm Paulino // Written for Bebelou Naidoo, April 2010

float TIMER_PERIOD = 5.0; // in seconds. integer RANDOM = 1; // 0 = sequential, 1 = random

// globals integer gNumTextures; integer gLastTexture;

default {

   on_rez (integer n)
   {
       llResetScript();
   }
   state_entry()
   {
       gLastTexture = 0;
       gNumTextures = llGetInventoryNumber(INVENTORY_TEXTURE);
       llSetTimerEvent(TIMER_PERIOD);
   }
   
   timer()
   {
       integer nextTexture;
       if (RANDOM)
       {
           // Randomly choose one
           nextTexture = (integer)llFrand (gNumTextures);
       }
       else
       {
           // Move on from the last one
           gLastTexture ++;
           if (gLastTexture > gNumTextures)
           {
               gLastTexture = 0;
           }
           nextTexture = gLastTexture;
       }
       string texture = llGetInventoryName(INVENTORY_TEXTURE, nextTexture);
       llSetTexture(texture, ALL_SIDES);
   }

} </lsl>

Phantom Prim Setter

<lsl> // Script to make a prim appear to be phantom, so it can be linked, etc, // without actually being phantom. // // Procedure: // 1. Create prim (not linked to anything) // 2. Put this script in it and let it run // 3. Link the prim to everything else // 4. Delete the script if you like (no reason to keep it) // // Basic technique found on the SL forums. // // Kimm Paulino // Written for Synonyme Toll, May 2010

default { on_rez (integer start_param) { llResetScript(); }

state_entry() { // This makes the prim appear to be 'phantom like' without // actually making it phantom ... llVolumeDetect(TRUE);

// Now disable the script once we've done our job llSetScriptState(llGetScriptName(), FALSE); } } </lsl>

Floating Script

<lsl> // Script for applying a small, random impulse to physical objects // to make them float about. // // Note: There is no bound to where the objects go, this script // is meant for objects in a physically bounded area (like a fish tank with a lid) // // Feel free to use as you wish, but do let me know if you find something // interesting to do with it ... // // Kimm Paulino // Written for Synonyme Toll, Sept 2009 //

// How often to apply the new impulse (in seconds) float IMPULSE_TIME = 3.0;

// Range for the magnitude and x, y, z directions of the impulse float IMPULSE_RANGE = 0.5;

float newRand (float range) {

   // Want a random number in range +- 'range'
   return (llFrand(2.0*range) - range);

}

applySmallImpulse () {

   vector my_vec = llGetVel();
   float  my_mag = llVecMag (my_vec);
   vector my_dir = llVecNorm (my_vec);
   // Change the magnitude slightly ...
   my_mag = my_mag + newRand(IMPULSE_RANGE);
   
   // Change the direction slightly too ...
   my_dir = <my_dir.x + newRand(IMPULSE_RANGE), my_dir.y + newRand(IMPULSE_RANGE), my_dir.z + newRand(IMPULSE_RANGE)>;
   
   vector new_vec = my_dir * my_mag;
   
   //llOwnerSay ("Applying Impulse: " + (string)new_vec);
   // apply the impulse to us
   llApplyImpulse (new_vec, TRUE);

}

default {

   on_rez(integer n)
   {
       llResetScript ();
   }
   
   state_entry()
   {
       // Turn on Physics
       llSetPrimitiveParams ([PRIM_PHYSICS, TRUE]);

// The following will stop it rotating and keep it facing the same direction if required // llSetStatus(STATUS_ROTATE_X|STATUS_ROTATE_Y|STATUS_ROTATE_Z, FALSE);

       // Set the boyancy to > 1.0 so that it floats
       llSetBuoyancy (1.0);
       
       // Set up a timer to apply an impulse
       llSetTimerEvent (IMPULSE_TIME);
   }
   
   timer()
   {
       applySmallImpulse();
   }

} </lsl>

Sit and Play Animation

<lsl> // Sets a sit target and plays whatever animation is // stored in the contents on the prim when someone sits down. // // Kimm Paulino // Written for Synonyme Toll, April 2010

// These numbers are totally dependent on the object containing // the script, and possibly even the animation to be used too. vector gPosition = <0.0, 0.0, 0.1>; vector gRotation = <0.0, 0.0, 0.0>; // in degrees

default {

   on_rez (integer start_param)
   {
       llResetScript();
   }
   
   state_entry()
   {
       // These numbers are totally dependent on the object containing the script!
       llSitTarget (gPosition, llEuler2Rot (gRotation * DEG_TO_RAD));
   }
   changed (integer change)
   {
       // When someone sits on an object, the av is considered to be
       // a linked-in child prim, so the link number changes
       if (change & CHANGED_LINK)
       {
           key av = llAvatarOnSitTarget();
           if (av)
           {
               // yes so need to play the animation
               // first request permissions - results in the callback ...
               llRequestPermissions(av, PERMISSION_TRIGGER_ANIMATION);
           }
       }
   }
   
   run_time_permissions(integer perms)
   {
       // Do we have permissions to run the animations?
       // results in the timer!
       if(perms)
       {
           llSetTimerEvent(1.0);
       }
       else
       {
           llSetTimerEvent(0.0);
       }
   }
   
   timer()
   {
       key av = llAvatarOnSitTarget();
       // If the av is still sitting, play the animation stored in the prim
       if (av)
       {
           llStopAnimation("sit");
           llStartAnimation( llGetInventoryName( INVENTORY_ANIMATION, 0 ));
       }
       else
       {
           // stop playing the animations for sitting if the av
           // is no longer sitting ...
           llStopAnimation("sit_generic");
           llSetTimerEvent(0.0);
       }
   }

} </lsl>

Move up and down

<lsl> // Makes an object go up or down, gradually, when touched. // In order to make it a gradual movement, it uses a physical // object, which means it might be a little wobbly ... // // Kimm Paulino // Written for Nicollette Arabello, November 2009 //

integer gState; integer DOWN=0; integer GO_UP=1; integer UP=2; integer GO_DOWN=3;

float MOVE_DAMPING=4.0; float MOVE_TIME=4.0;

// Move to 1m above starting position vector gOffset = <0.0, 0.0, 1.0>; vector gStartPosition;

default {

   state_entry()
   {
       // Use physics so that can use MoveToTarget
       gStartPosition = llGetPos ();
       
       // Stop the object rotating
       llSetStatus(STATUS_ROTATE_X|STATUS_ROTATE_Y|STATUS_ROTATE_Z, FALSE);
       llSetStatus(STATUS_PHYSICS, TRUE);
       llMoveToTarget (gStartPosition, MOVE_DAMPING);
       llSetTimerEvent (MOVE_TIME);
   }
   
   on_rez(integer n)
   {
       llResetScript();
   }
   timer ()
   {
       if (gState == DOWN)
       {
           gState = GO_UP;
           llMoveToTarget(gStartPosition + gOffset, MOVE_DAMPING);
           gState = UP;
       }
       else if (gState == UP)
       {
           gState = GO_DOWN;
           llMoveToTarget(gStartPosition, MOVE_DAMPING);
           gState = DOWN;
       }
   }

} </lsl>

Simple Door

<lsl> // Was asked to modify an existing door script. I was just given // this to update so afraid I can't give credit, but if you know where // the original came from, do let me know. // // Swinging door LSL script #1 // Handles the touch event. // Handles the collision event. // Handles closing the door automatically via a timer event. // Triggers sounds when the door opens or closes. // // Updated by Kimm Paulino, April 2010, for Synonyme Toll, // to use local coords for the rotations instead of global // coords. // // Updated by Kimm Paulino, May 2010, for Synonyme Toll, // to use messages to link two doors together. // // Note: For this to work, you need a door with a path cut // of 0.375 to 0.875 and a width that is twice as wide // as you need (the cut brings it back down to the right // size). Without this, it will just rotate on the spot, // not swing at the edge. //

// Parameters you might want to change

float delay = 3.0; // time to wait before automatically closing door

                               // set to 0.0 to not automatically close

float direction = 1.0; // set to 1.0 or -1.0 to control direction the door swings float volume = 0.5; // 0.0 is off, 1.0 is loudest integer linked_doors = 0; // set to 1 if using this script in a linked set of doors float angle = 90.0; // angle to open in degrees integer coll_detect = 1; // set to 1 to make door open on approach

// Variables you will most likely leave the same

key open_sound = "cb340647-9680-dd5e-49c0-86edfa01b3ac"; key close_sound = "e7ff1054-003d-d134-66be-207573f2b535";

// KP: Link send/recv functions // IMPORTANT // If you only have one pair of doors using this script, then // these will be fine. If you have several doors using this // script that you don't want all opening together, then // you'll have to change these to something unique. // integer LINK_MSG_NUM = 452345; string LINK_MSG_STR_OPEN = "Open the door"; string LINK_MSG_STR_CLOSE = "Close the door";

linkSend (string dir) {

   if (linked_doors)
   {
       // Note: Could try to work out the link numbers of the doors,
       // but simplest is to send to all other prims in the build.
       llMessageLinked (LINK_ALL_OTHERS, LINK_MSG_NUM, dir, NULL_KEY);
   }

}

integer linkRecv (integer sender_num, integer num, string str, key id, string str_to_check) {

   if (linked_doors)
   {
       // Check it really is a message for these doors
       if (num == LINK_MSG_NUM)
       {
           if (str == str_to_check)
           {
               // All good
               return 1;
           }
       }
   }
   
   // not for us
   return 0;

}

// Processing for the script when it first starts up

default {

   // What we do when we first enter this state
   state_entry() {
       state open;                        // Move to the open state
   }

}

// Processing for the script when it is in the closed state

state closed {

   // What we do when we first enter this state
   state_entry() {
       llTriggerSound(close_sound, volume); // Trigger the sound of the door closing
       llSetLocalRot(llEuler2Rot(<0, 0, direction * angle * DEG_TO_RAD>) * llGetLocalRot());
   }
   // What we do when the door is clicked (”touched”) with the mouse
   touch_start(integer total_number) {
       linkSend (LINK_MSG_STR_OPEN);  // KP: Tell everyone else we are now in the open state
       state open;                        // Move to the open state
   }
   // What to do when something hits the door 
   collision_start(integer total_number)
   {
       if (coll_detect != 0)
       {
           linkSend (LINK_MSG_STR_OPEN);  // KP: Tell everyone else we are now in the open state
           state open;                        // Move to the open state
       }
   }
   
   // KP: Handle link messages
   
   link_message (integer sender_num, integer num, string str, key id)
   {
       if (linkRecv (sender_num, num, str, id, LINK_MSG_STR_OPEN))
       {
           state open;                    // Move to the open state
       }
   }
   // What to do when the timer goes off
   timer()
   {
       llSetTimerEvent(0.0);              // Set the timer to 0.0 to turn it off
   }

}

// Processing for the script when it is in the open state

state open {

   // What we do when we first enter this state
   state_entry() {
       llTriggerSound(open_sound, volume);// Trigger the sound of the door opening
       llSetLocalRot(llEuler2Rot(<0, 0, -direction * angle * DEG_TO_RAD>) * llGetLocalRot());
       llSetTimerEvent(delay);            // Set the timer to automatically close it
   }
   // What do do when pulling the door from Inventory if it was saved while open
   on_rez(integer start_param) {
       state closed;
   }
   // What we do when the door is clicked (”touched”) with the mouse
   touch_start(integer total_number) {
       linkSend (LINK_MSG_STR_CLOSE);  // KP: Tell everyone else we are now in the open state
       state closed;                      // Move to the closed state
   }
   // What to do when something hits the door 
   collision_start(integer total_number)
   {
       // Do nothing, the door is already open
   }
   // KP: Handle link messages
   
   link_message (integer sender_num, integer num, string str, key id)
   {
       if (linkRecv (sender_num, num, str, id, LINK_MSG_STR_CLOSE))
       {
           state closed;                    // Move to the open state
       }
   }
   // What to do when the timer goes off
   timer()
   {
       llSetTimerEvent(0.0);             // Set the timer to 0.0 to turn it off
       state closed;                     // Move to the closed state
   }

} </lsl>

Child Prim Rotations

This is a test script to pull together all the advice about rotations about a centre of rotation, in local coordinates with the appropriate work arounds for setting rotations, etc. Not really meant for use on its own but just to highlight the logic for applying the rotations.

<lsl> // Testing child prim rotations. // This is a script that could be used with a non-path-cut linked door or windows // and so on, with the correct rotation offsets and calculations to function. // // When the script is run for the first time, prim should be in the closed position. // // Kimm Paulino, August 2010

// Specify the rotation to apply, as a Euler vector in degrees vector gNewRotation_e = <0.0, 0.0, 150.0>;

// Specify the details of the Centre of Rotation to use. // Can either work it out from the prim parameters or put a hardcoded one in here integer gCorAxis = 1; // 1=x, 2=y, 3=z, -1=-x, -2=-y, -3=-z, 0=gCor vector gCor = <0.0,0.0,0.0>; // Relative to root prim, if gCorAxis = 0

// This test script will automatically return the prim to its start // parameters after this time if gAutoClose is set to TRUE. float RESET_TIME = 5.0; integer gAutoClose = FALSE;

vector gInitialPosition; rotation gInitialRotation; integer CLOSED=0; integer OPEN=1; integer gState;

store_child () { // llOwnerSay ("-----------------");

   gInitialPosition = llGetLocalPos();

// llOwnerSay ("Initial Position: " + (string)gInitialPosition);

   gInitialRotation = llGetLocalRot();

// llOwnerSay ("Initial Rotation: " + (string)r2v(gInitialRotation)); }

restore_child() {

   // Note: Use the PRIM_ROTATION workaround, as described in SVC-93
   llSetPrimitiveParams ([    PRIM_POSITION, gInitialPosition,
                                   PRIM_ROTATION, gInitialRotation / llGetRootRotation()]);
   //llSetPos (gInitialPosition);
   //llSetLocalRot (gInitialRotation);

}

vector calcCorAxis () {

   // Note: If the prim is rotated, then we need to apply the
   // same rotation to the size values to pick up the correct axis
   vector prim_size = llGetScale() * llGetLocalRot();
   if (gCorAxis == 1)
   {
       return <(prim_size.x/2.0), 0.0, 0.0>;
   }
   else if (gCorAxis == 2)
   {
       return <0.0, (prim_size.y/2.0), 0.0>;
   }
   else if (gCorAxis == 3)
   {
       return <0.0, 0.0, (prim_size.z/2.0)>;
   }
   else if (gCorAxis == -1)
   {
       return <(-prim_size.x/2.0), 0.0, 0.0>;
   }
   else if (gCorAxis == -2)
   {
       return <0.0, (-prim_size.y/2.0), 0.0>;
   }
   else if (gCorAxis == -3)
   {
       return <0.0, 0.0, (-prim_size.z/2.0)>;
   }
   else
   {
       return gCor;
   }

}

vector r2v (rotation r) {

   return (RAD_TO_DEG * llRot2Euler (r));

}

// rot is a rotation to be applied to the prim // cor is a relative position (to the root) for the centre of rotation rotate_child (rotation rot, vector cor) {

   // Work in local coordinates
   vector current_position = llGetLocalPos();
   rotation current_orientation = llGetLocalRot();
   //llOwnerSay ("Current position/rotation: " + (string)current_position + " / " + (string)r2v(current_orientation));

   // Calculate the offset from the centre of the object
   // to the centre of rotation.  This effectively moves
   // the object so that the cor can be thought off as the
   // origin.  Once we've done the calculations, we'll move it back.
   vector normalised_position = current_position - cor;
   //llOwnerSay ("Normalised position/COR: " + (string)normalised_position + " / " + (string)cor);

   // Calculate the new position by applying the required
   // rotation to the current position (i.e. rotate the
   // vector origin-position to produce origin-newposition)
   vector new_normalised_position = normalised_position * rot;
   //llOwnerSay ("Rotated Normalised Position: " + (string)new_normalised_position);
   vector new_position = cor + new_normalised_position;
   //llOwnerSay ("New Actual Position: " + (string)new_position);

   rotation new_orientation = current_orientation * rot;
   //llOwnerSay ("New Orientation: " + (string)r2v(new_orientation));

   // Set local position and rotation
   // Note: There is no llSetLocalPos - llSetPos will do local coords for a child
   //llSetPos (new_position);
   //llSetLocalRot (new_orientation);

   // However, use llSetPrimitiveParams to set both at same time, without
   // incurring two 0.2s delays ... although note, have to use
   // the PRIM_ROTATION workaround, as described in SVC-93
   llSetPrimitiveParams ([    PRIM_POSITION, new_position,
                                   PRIM_ROTATION, new_orientation / llGetRootRotation()]);

}

default {

   on_rez (integer start_param)
   {
       // As positions will be stored on entry,
       // don't want to auto reset on rez, as it might
       // be rezed in the closed position, which would screw
       // things up!

// llResetScript();

   }

   state_entry ()
   {
       // store initial position/etc
       gState = CLOSED;
       store_child ();
   }

   touch_start (integer num_detected)
   {
       if (gState == CLOSED)
       {
           // Need to convert CoR to local coordinates relative to
           // the root prim (not just relative to this prim).
           vector cor = llGetLocalPos() + calcCorAxis();
           rotate_child (llEuler2Rot (gNewRotation_e * DEG_TO_RAD), cor);
           if (gAutoClose)
           {
               llSetTimerEvent (RESET_TIME);
           }
           gState = OPEN;
       }
       else
       {
           restore_child ();
           gState = CLOSED;
       }
   }

   timer ()
   {
       gState = CLOSED;
       restore_child ();
       llSetTimerEvent (0.0);
   }

} </lsl>

Prim Reducing Script

This will reduce a prim's size by scaling it by the specified number of steps and applying that scaling the specified number of times using a timer.

<lsl> // Reducing Script // // Kimm Paulino // Written for Stewart Bosatsu, Sept 2010

integer TIMER_STEPS = 18; float REDUCING_STEPS = 20; // If this >= TIMER_STEPS then prim will disappear float TIMER_INTERVAL = 2.0; // In seconds integer gCount; vector gReducingFactor;

default {

   on_rez (integer start_param)
   {
       llResetScript();
   }
   
   state_entry()
   {
       gCount = 0;
       vector size = llGetScale();
       float scaling = 1.0 / REDUCING_STEPS;
       gReducingFactor = size * scaling;
       
   }
   touch_start(integer total_number)
   {
       llSetTimerEvent (TIMER_INTERVAL);
   }
   
   timer ()
   {
       // Reduce the size by 1/TIMER_STEPS % each time
       gCount ++;
       if (gCount > TIMER_STEPS)
       {
           // disable and quit
           llSetTimerEvent (0.0);
           llDie();
           return;
       }
       
       // Reduce prim
       vector size = llGetScale();
       
       size = size - gReducingFactor;
       llSetScale (size);
   }

} </lsl>

Simple Path Script

<lsl> // Path Setting Script // // In 'edit' mode, user moves the object and 'saves' various positions // over time, then the positions can be replayed. This either uses the physical // move functions to create smooth movement or non-physical movements // for a slightly more jerky movement! // // NOTE: Positions and rotations are relative to the region, so if you // move the prim, then the positions won't move with it - you'd have to // reset the script (using the 'reset' button) and store a new path. // // Depending on the settings, the system can either loop forever // or play just once. // // It also has the option of resetting if you change owners, which // might be useful if you want new owners to be able to store their // own paths. // // Kimm Paulino // Oct 2010

integer gDebug = FALSE; integer gPhysics = FALSE; // Set to use physical movements integer gLoop = TRUE; // Set to continually loop through the movements integer gResetOnOwnerChange = TRUE; // Set if want script to auto reset when changing owners

list gPositionData; // Always assume that there are the same numbers list gRotationData; // of position and rotation data points integer gCurrentIdx; float gTimePeriod = 2.0; float gTau = 5.0; key gOwnerId; integer gListen; integer gTimeHandle; integer gTauHandle; string gHelpMsg = "Use EDIT mode to move your object, selecting 'Save' to save each position. Select 'Done' once complete. Don't forget to save your first position too!"; string gErrorMsg = "Something unexpected went wrong, suggest you reset the script!"; string SAVE_BTN = "Save"; string DONE_BTN = "Done"; string TIME_BTN = "Time Adjust"; string TAU_BTN = "Tau Adjust"; string RESET_BTN = "Reset"; string START_BTN = "Start"; string STOP_BTN = "Stop"; string START_MSG = "start"; // What a passer by can type in via chat integer LISTEN_CH = 600; integer TIME_CH = 900; integer TAU_CH = 901;

doDebug (string msg) {

   if (gDebug)
   {
       llOwnerSay (msg);
   }

}

doMove() {

   integer num_points = llGetListLength(gPositionData); 
   if (num_points != llGetListLength (gRotationData))
   {
       llOwnerSay (gErrorMsg);
       disableMove();
       return;
   }
   
   if (gCurrentIdx >= num_points)
   {
       if (gLoop)
       {
           // Loop around for another go
           gCurrentIdx = 0;
       }
       else
       {
           // All complete
           disableMove();
           return;
       }
   }
   
   doDebug ("Moving to position " + (string)gCurrentIdx);
   
   vector next_pos = llList2Vector (gPositionData, gCurrentIdx);
   rotation next_rot = llList2Rot (gRotationData, gCurrentIdx);
   
   if (next_pos == ZERO_VECTOR && next_rot == ZERO_ROTATION)
   {
       // ignore
   }
   else
   {
       if (gPhysics)
       {
           llMoveToTarget(next_pos, gTau);
           llLookAt(next_pos,1,1);
           llRotLookAt(next_rot,1,1);
       }
       else
       {

// doDebug ("moving to: " + (string)next_pos);

           llSetRot (next_rot);
           llSetPos (next_pos);
       }
   }
   // Move on to the next point
   gCurrentIdx ++;

}

dialog () {

   list buttons;
   if (gPhysics)
   {
       buttons = [SAVE_BTN, DONE_BTN, RESET_BTN, START_BTN, STOP_BTN, TIME_BTN, TAU_BTN];
   }
   else
   {
       buttons = [SAVE_BTN, DONE_BTN, RESET_BTN, START_BTN, STOP_BTN, TIME_BTN];
   }
   llDialog (gOwnerId, gHelpMsg, buttons, LISTEN_CH);

}

enableMove () {

   if (gPhysics)
   {
       doDebug ("Enabling physical move");
       llSetStatus (PRIM_PHYSICS, TRUE);
   }
   else
   {
       doDebug ("Enabling non-physical move");
       llSetStatus(PRIM_PHYSICS, FALSE); 
   }
   llSetTimerEvent (gTimePeriod);
   gCurrentIdx = 0;
   doMove ();

}

disableMove () {

   doDebug ("Disabling move");
   llSetStatus (PRIM_PHYSICS, FALSE);
   llSetTimerEvent (0.0);

}

default {

   on_rez (integer start_param)
   {
       // if we reset on rez, then a user can't take an object into
       // inventory have rerez it with the same path stored.
       //
       // Means that if they do want to clear the path, say because
       // the position in the Sim has changed, then they have to use
       // the 'reset' option.
   }
   
   state_entry() 
   {   
       llOwnerSay ("Ready to start saving positions.  Touch for menu, then go to SL Edit mode to move the object and use 'save' on the menu to save each position.");
       gOwnerId = llGetOwner();
   }
   
   touch_start(integer who)
   {
       gListen = llListen (LISTEN_CH,"",NULL_KEY,"");
       if (llDetectedKey(0) == gOwnerId)
       {
           dialog();
       }
       else
       {
           if (!gLoop)
           {
               // Let nearby users start the moving
               llWhisper  (0, "To start the movement, please type the following into local chat:  /" + (string)LISTEN_CH + " " + START_MSG);
           }
       }
   }
   
   listen (integer channel, string name, key id, string msg)
   {
       vector pos = llGetPos();
       rotation rot = llGetRot();
       
       if (channel == LISTEN_CH)
       {
           if (msg == START_BTN || msg == START_MSG)
           {
               enableMove();
           }
           // non-owners can't do anything else
           if (id != gOwnerId)
           {
               return;
           }
               
           if (msg == SAVE_BTN)
           {
               gPositionData += pos;
               gRotationData += rot;
               dialog ();
           } 
           else if (msg == STOP_BTN)
           {
               disableMove();
           }
           else if (msg == RESET_BTN)
           {
               llResetScript();
           }
           else if (msg == TIME_BTN)
           {
               gTimeHandle = llListen (TIME_CH, "", gOwnerId, "");
               llOwnerSay ("Adjust time using: /" + (string)TIME_CH + " <float seconds>");
           } 
           else if (msg == TAU_BTN)
           {
               gTauHandle = llListen(TAU_CH, "", gOwnerId, "");
               llOwnerSay ("Adjust Tau using: /" + (string)TAU_CH + " <float value>");
           } 
           else if (msg == DONE_BTN)
           {
               llOwnerSay("To reset use: /" + (string)LISTEN_CH + " reset");
               llOwnerSay("To start use: /" + (string)LISTEN_CH + " start");
           }
       }
       
       if (channel == TIME_CH)
       {
           gTimePeriod = (float)msg;
           llListenRemove (gTimeHandle);
           llOwnerSay ("Time period set to " + msg);
       } 
       if (channel == TAU_CH)
       {
           gTau = (float)msg;
           llListenRemove (gTauHandle);
           llOwnerSay ("Tau set to " + msg);
       } 
   }
    
   changed(integer ch)
   {
       if(ch & CHANGED_OWNER)
       {
           if (gResetOnOwnerChange)
           {
               // This will clear out all stored positions of course!
               llResetScript();
           }
       }
   }
   timer()
   {
        doMove();
   }

} </lsl>

Visitor Counter and LM Giver

<lsl> // Simple visitor counter and lm giver script // // Just finds the first landmark stored in the prim and // gives it to non-owners on touch. // // For owners, if the prim is touched, presents a simple // dialog to either list visitors or reset the visitors counts. // // Kimm Paulino // Written for Vikki Hastings, Oct 2010

// Configure the behaviour we want integer gLogDates = TRUE; // Include timestamps in the log integer gJustDates = FALSE; // But only dates (not timestamps) integer gKeepFirst = FALSE; // Keep the first visit of someone, not the last string gFloatingText = ""; // Set to "" to disable vector gFloatingTextColour = <1.0, 1.0, 1.0>; float gFloatingTextAlpha = 1.0; float gSensorRange = 15.0;

integer MAX_VISITORS = 30; list gVisitors; list gVisitorTimes; integer gVisitorCount; integer gChannel; integer gListenHandle;

integer getRandomChannel() {

   // Some magic I got of the SL wiki somewhere ...
   return -llRound(llFrand( llFabs(llSin(llGetGMTclock())) * 1000000 )) - 11;

}

listVisitors () {

   llOwnerSay("----------------");
   llOwnerSay("Total number of visits: " + (string)gVisitorCount);
   if (gVisitorCount > 0)
   {
       llOwnerSay("Most recent visitors:");
       integer i;
       integer len = llGetListLength(gVisitors);
       for (i=0; i < len; i++)
       {
           if (gLogDates)
           {
               llOwnerSay("[" + llList2String (gVisitorTimes, i) + "] " +llList2String(gVisitors,i));
           }
           else
           {
               llOwnerSay(llList2String(gVisitors,i));
           }
       }
   }

}

resetVisitors() {

   llOwnerSay ("Resetting visitor count");
   gVisitors = [];
   gVisitorTimes = [];
   gVisitorCount = 0;

}

addVisitor(string name) {

   // see if we've already seen this one
   integer idx = llListFindList(gVisitors, (list)name);
   if (idx != -1 )
   {
       // Already in the list, so:
       // If storing dates, then need to decide if we are keeping
       // the first visit or the last
       //
       // If we are not keeping dates, just don't bother logging again
       if (!gLogDates)
       {
           return;
       }
       
       if (gKeepFirst)
       {
           // Already have the entry we wish to keep ...
           return;
       }
       
       // ok, we are keeping the last visits, so remove
       // the name from the list and let the rest of the function
       // add it in again!
       gVisitors = llDeleteSubList (gVisitors, idx, idx);
       gVisitorTimes = llDeleteSubList (gVisitorTimes, idx, idx);
   }
   else
   {
       // Note:  Will count people who visited before but have
       // now dropped off the end of the 'last visitors' list, but
       // thats all.
       gVisitorCount ++;
   }
   //llOwnerSay ("Adding " + name);
   gVisitors += name;
   if (gJustDates)
   {

gVisitorTimes += llGetDate();

   }
   else
   {
       list timestamp = llParseString2List(llGetTimestamp(),["T","."],[""]);

string time = llList2String(timestamp, 0) + " " + llList2String(timestamp, 1); gVisitorTimes += time;

   }
   // See if we are at the end of the list
   // this helps to keep memory limits down
   // and also creates a rolling list of most
   // recently seen avs
   if (llGetListLength(gVisitors) > MAX_VISITORS)
   {
       // Remove first entry in the list
       gVisitors = llDeleteSubList (gVisitors, 0, 0);
       gVisitorTimes = llDeleteSubList (gVisitorTimes, 0, 0);
   }

}

default {

   on_rez (integer start_param)
   {
       llResetScript();
   }
   state_entry()
   {
       resetVisitors();
       gChannel = getRandomChannel();
       
       llSetText (gFloatingText, gFloatingTextColour, gFloatingTextAlpha); 
       
       // Range, angle, rate
       // So 5m range, 180 deg, every 5 secs
       llSensorRepeat ("", NULL_KEY, AGENT, gSensorRange, PI, 5.0);
   }
   sensor(integer total_number)
   {
       integer i;
       //llSay (0, "Found " + (string)total_number);
       for (i=0 ; i<total_number ; i++)
       {
           key avid = llDetectedKey(i);
           string av = llKey2Name (avid);
           //llSay (0, "Detected " + av);
       
           if (avid == llGetOwner ())
           {
               // Don't count owner
               //llSay (0, "Ignoring owner");
           }
           else
           {
               //llSay (0, "Adding visitor");
               addVisitor (av);
           }
       }
   }
   
   touch_start(integer total_number)
   {
       // Two things to do on touch
       // If its the owner, dump list of visitors
       // If its not, then give a lm if one exists
       string visitor = llDetectedKey (0);
       if (visitor == llGetOwner())
       {
           llListenRemove (gListenHandle); // in case things didn't complete last time
           llSetTimerEvent (600.0);  // In 10 minutes, all times out
           gListenHandle = llListen (gChannel, "", "", "");
           llDialog (visitor, "Kimm's Visitor Tracker", ["List", "Reset"], gChannel);
       }
       else
       {
           // get the name of the first landmark in the inventory
           string lm = llGetInventoryName(INVENTORY_LANDMARK, 0);
           if (lm != "")
           {
               llGiveInventory(visitor, lm); 
           }
       }
   }
   
   listen (integer channel, string name, key id, string msg)
   {
       if (channel != gChannel)
       {
           // not for us ...
           return;
       }
       
       if (msg == "List")
       {
           listVisitors();
       }
       else if (msg == "Reset")
       {
           resetVisitors();
       }
       
       llListenRemove (gListenHandle);
       gListenHandle = 0;
   }
   
   timer ()
   {
       llListenRemove (gListenHandle);
       gListenHandle = 0;
       llSetTimerEvent (0.0);
   }

} </lsl>

LM, Notecard, Group, URL giver

<lsl> // Simple lm giver script // // Just finds the first landmark stored in the prim and // gives it on touch. // // Also, if configured, will give out a URL or a link to a group. // // If a notecard is present in the inventory, it can give that too. // // If any of these things are not present, then they will not be // presented as an option. // // Kimm Paulino // Written for Vikki Hastings, Jan 2011

// Configure the behaviour we want string gFloatingText = "Click for Landmark"; // Set to "" to disable vector gFloatingTextColour = <1.0, 1.0, 1.0>; float gFloatingTextAlpha = 1.0;

string gLm1; string gLm2; string gNC; string gUrl="https://wiki.secondlife.com/wiki/User:Kimm_Paulino/Scripts"; // HTTP URL to give out string gGroup="OBJECT"; // UUID only for the group or OBJECT if it is to take the prims group integer gChannel;

integer getRandomChannel () {

   // Based on http://tali.appspot.com/html/scripting/snippets.html
   // Always leaves 17th bit set (so never a number less than 65535)
   // Always leaves sign bit unset (so is always positive)
   integer pos_int = (((integer)llFrand(16384)) << 17) | 65536 | ((integer)llFrand(65535));
   return -pos_int;

}

give_landmark (key av) {

   if (gLm1 != "")
   {
       llGiveInventory(av, gLm1); 
   }
   if (gLm2 != "")
   {
       llGiveInventory(av, gLm2);
   }

}

give_notecard (key av) {

   if (gNC != "")
   {
       llGiveInventory(av, gNC); 
   }    

}

give_link (key av) {

   if (gUrl != "")
   {
       llLoadURL (av, "Giving Link", gUrl);
   }

}

give_group (key av) {

   if (gGroup != "")
   {
       llWhisper (0, "Click on the following link to join the group\nsecondlife://app/group/" + gGroup + "/about");
   }

}

default {

   on_rez (integer start_param)
   {
       llResetScript();
   }
   state_entry()
   {
       if (gGroup == "OBJECT")
       {
           // Use the group from the prim
           gGroup = llList2Key(llGetObjectDetails(llGetKey(), [OBJECT_GROUP]), 0);
       }
       
       // See if there are landmarks to give out
       gLm1 = llGetInventoryName(INVENTORY_LANDMARK, 0);
       gLm2 = llGetInventoryName(INVENTORY_LANDMARK, 1);
       gNC = llGetInventoryName (INVENTORY_NOTECARD, 0);
       
       llSetText (gFloatingText, gFloatingTextColour, gFloatingTextAlpha);
       gChannel = getRandomChannel();
       
       llListen (gChannel, "", "", "");
   }
   touch_start(integer total_number)
   {
       list buttons=[];
       if (gLm1 != "")
       {
           buttons += ["Landmark"];
       }
       if (gUrl != "")
       {
           buttons += ["Link"];
       }
       if (gGroup != "")
       {
           buttons += ["Group"];
       }
       if (gNC != "")
       {
           buttons += ["Notecard"];
       }
       integer i;
       for (i=0; i<total_number; i++)
       {
           llDialog (llDetectedKey(i), "Please select", buttons, gChannel);
       }
   }
   
   listen (integer channel, string name, key id, string message)
   {
       if (channel != gChannel)
       {
           return;
       }
       
       if (message == "Landmark")
       {
           give_landmark(id);
       }
       else if (message == "Link")
       {
           give_link(id);
       }
       else if (message == "Group")
       {
           give_group(id);
       }
       else if (message == "Notecard")
       {
           give_notecard(id);
       }
   }
   
   changed(integer change)
   {
       if (change & CHANGED_INVENTORY)         
       {
           llResetScript();
       }
   }

} </lsl>

HTML Page from a notecard on a prim

<lsl> // Example to show how a basic HTML page can be read // from a notecard and then displayed on the face of a prim. // // Will also request a tinyurl to give to users on touch // - useful for non-viewer 2.0 clients - they can still // see the data/page but in an external browser instead. // // Kimm Paulino, Jan 2011

// Note that the HTML can only be 1024 characters long - no more! integer HTML_FACE_DISPLAY = 2; // See http://wiki.secondlife.com/wiki/Face

string HTML_DEFAULT = "<head><style>body {background-color:#808000;}</style></head><body>

HTML on a Prim Example

Kimm Paulino, Jan 2011

</body>";

// Used when processing notecard-based questions only string HTML_NOTECARD_NAME="htmlpage"; key gNCQueryId; integer gNCLine; string gHtmlPage=""; string gTinyUrl = ""; key gUrlReqId;

check_notecards() {

   integer nccount = llGetInventoryNumber (INVENTORY_NOTECARD);
   integer i;
   string  htmlnote;
   gHtmlPage = "";
   
   for (i=0 ; i<nccount ; i++)
   {
       htmlnote = llGetInventoryName (INVENTORY_NOTECARD, i);
       if (htmlnote == HTML_NOTECARD_NAME)
       {
           gNCLine = 0;
           gNCQueryId = llGetNotecardLine (HTML_NOTECARD_NAME, gNCLine);
           return;
       }
   }
       
   // if got here then there is no notecard with HTML info in it,
   // so show the current page and return
   show_html_page();

}

show_html_page () {

   string html = "data:text/html,";
   if (gHtmlPage != "")
   {
       html += llEscapeURL (gHtmlPage);
   }
   else
   {
       html += llEscapeURL (HTML_DEFAULT);
   }
   // note that this is limited to 1024 characters
   // See: http://wiki.secondlife.com/wiki/User:Kelly_Linden/lsl_hacks
   llOwnerSay ("HTML Page Size: " + (string)llStringLength (html));
   llSetPrimMediaParams(HTML_FACE_DISPLAY,    // Side to display the media on.
           [PRIM_MEDIA_AUTO_PLAY,TRUE,        // Show this page immediately
           PRIM_MEDIA_CURRENT_URL,html,        // The url currently showing
           PRIM_MEDIA_HOME_URL,html,            // The url if they hit 'home'
           PRIM_MEDIA_HEIGHT_PIXELS,512,        // Height/width of media texture will be
           PRIM_MEDIA_WIDTH_PIXELS,512,        //   rounded up to nearest power of 2.
            PRIM_MEDIA_AUTO_SCALE, FALSE,
           PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI
           ]);
   // Request a new Tiny URL for this data
   gUrlReqId = llHTTPRequest("http://tinyurl.com/api-create.php?url=" + html, [], "");

}

default {

   on_rez (integer start_param)
   {
       llResetScript();
   }
   
   state_entry ()
   {
       check_notecards();
   }
   
   touch_start (integer num_detected)
   {
       if (gTinyUrl != "")
       {
           llLoadURL (llDetectedKey(0), "Media on a Prim URL", gTinyUrl);
       }
   }
   
   dataserver (key query_id, string data)
   {
       if (query_id != gNCQueryId)
       {
           // Not for us
           return;
       }
       if (data == EOF)
       {
           // All done
           show_html_page();
           return;
       }
       
       // Otherwise add the data from the notecard to the CSS string
       gHtmlPage += data;
       
       // and read the next line
       gNCLine++;
       gNCQueryId = llGetNotecardLine(HTML_NOTECARD_NAME, gNCLine);
   }
   
   changed (integer change)
   {
       if (change & CHANGED_INVENTORY)
       {
           check_notecards();
       }
   }
   
   http_response (key request_id, integer status, list metadata, string body)
   {
       if (request_id == gUrlReqId)
       {
           if (status == 200)
           {
               gTinyUrl = body;
               llOwnerSay ("Tinyurl obtained: " + gTinyUrl);
               gUrlReqId = "";
           }
           else
           {
               llOwnerSay ("http_response: Status=" + (string)status + " Body=" + body);
           }
       }
   }

} </lsl>

Multiple Notecard Giver

This populates a dialog based on the number of notecards in the inventory.

<lsl> // Script to present a dialog populated with a list of notecards // from the prims inventory // // Note that only the first 24 characters of the notecard names // are actually used - as only 24 chars can be placed in a dialog // button. This means that if two notecards have the first 24 chars // the same, the second will be ignored ... // // Note however that usually, buttons don't display more than, say, // around 10 or so characters anyway!! // // Kimm Paulino // Written for Vikki Hastings, June 2011

// Configure the behaviour we want string gFloatingText = "Click for notecards"; // Set to "" to disable vector gFloatingTextColour = <1.0, 1.0, 1.0>; float gFloatingTextAlpha = 1.0;

integer gChannel; integer gMenuPage; integer gNumNotecards; list gNotecards;

string MENU_NEXT="Next>"; string MENU_TOP="Top"; string MENU_BACK="<Back"; string MENU_SPACE=" ";

integer getRandomChannel () {

   // Based on http://tali.appspot.com/html/scripting/snippets.html
   // Always leaves 17th bit set (so never a number less than 65535)
   // Always leaves sign bit unset (so is always positive)
   integer pos_int = (((integer)llFrand(16384)) << 17) | 65536 | ((integer)llFrand(65535));
   return -pos_int;

}

read_notecards () {

   gNumNotecards = llGetInventoryNumber (INVENTORY_NOTECARD);
   gNotecards = [];
   
   if (gNumNotecards == 0)
   {
       llOwnerSay ("Note: There are no notecards in this prim to give to people ...");
       return;
   }
   
   integer i;
   for (i=0; i<gNumNotecards; i++)
   {
       string nc = llGetInventoryName (INVENTORY_NOTECARD, i);
       // Only store the first 24 characters of the name as this is all we can
       // show in a dialog anyway
       gNotecards += [llGetSubString (nc, 0, 23)];
   }

}

give_notecard (key id, string nc) {

   integer idx = llListFindList (gNotecards, [nc]);
   if (idx == -1)
   {
       // Didn't find it in the list - might not be a notecard
       return;
   }
   // Now need to find the notecard that matches from the inventory
   // and give it out
   integer i;
   for (i=0; i<gNumNotecards ; i++)
   {
       string nc_full = llGetInventoryName(INVENTORY_NOTECARD, i);
       if (llGetSubString (nc_full, 0, 23) == nc)
       {
           // This is the one
           llGiveInventory (id, nc_full);
           return;
       }
   }
   // if get here, then didn't get a match ... hmm

}

show_dialog (key id) {

   list buttons = [];
   
   if (gNumNotecards == 0)
   {
       llDialog (id, "There are no notecards to give at present.\nThe Owner doesn't appear to have put any in yet.", [], gChannel);
       return;
   }
   
   // If several people are using the giver, then it is possible
   // for gMenuPage to go negative or too high
   if (gMenuPage < 0)
   {
       // Reset ...
       gMenuPage = 0;
   }
   integer num_pages = ((gNumNotecards+8)/9);
   if (gMenuPage >= num_pages)
   {
       // gMenuPage is an index starting at 0...
       // max is a 1...
       gMenuPage = num_pages-1;
   }
   
   // Note: This yields notecards counting as follows:
   // 0 to 8 = first page,
   // 9 to 17 = next page, etc
   integer first_nc = gMenuPage * 9;
   integer last_nc = first_nc + 9 - 1;
   if (last_nc >= gNumNotecards)
   {
       // recall last_nc indexed from 0, but
       // gNumNotecards is indexed from 1
       last_nc = gNumNotecards-1;
   }

// llOwnerSay ("Menu Page: " + (string)gMenuPage); // llOwnerSay ("First NC: " + (string)first_nc); // llOwnerSay ("Last NC: " + (string)last_nc);

   if (gMenuPage > 0)
   {
       buttons += [MENU_BACK];
   }
   else
   {
       buttons += [MENU_SPACE];
   }
   if (gMenuPage == 0)
   {
       buttons += [MENU_SPACE];
   }
   else
   {
       
       buttons += [MENU_TOP];
   }
   // If not on the last page, and there are more pages to come ...
   if (gNumNotecards > 9 && gMenuPage < (num_pages-1))
   {
       buttons += [MENU_NEXT];
   }
   else
   {
       buttons += [MENU_SPACE];
   }
   integer i;
   for (i=first_nc; (i <= last_nc) && (i < gNumNotecards); i++)
   {
       buttons += [llList2String (gNotecards, i)];
   }
   llDialog (id, "\n\n(click \"Ignore\" when done)", buttons, gChannel);

}

default {

   on_rez (integer start_param)
   {
       llResetScript();
   }
   state_entry()
   {
       llSetText (gFloatingText, gFloatingTextColour, gFloatingTextAlpha);
       read_notecards();
       gMenuPage = 0;
       gChannel = getRandomChannel();
       
       // Would be less laggy to close listen when not in use
       // but then if two people attempted to use the giver,
       // it wouldn't be able to cope.  This way, they can both
       // use it at the same time ...
       llListen (gChannel, "", "", ""); 
   }
   touch_start(integer total_number)
   {
       // Start menu system again. Note that I don't do anything special
       // with several people trying to touch it at the same time - it will
       // always overlap the processing of gMenuPage, but all that will
       // happen is that peoples next/back might be a bit quirky for a while.
       // Eventually, they will sort themselves out!
       gMenuPage = 0;
       show_dialog (llDetectedKey(0));
   }
   listen (integer channel, string name, key id, string msg)
   {
       if (channel == gChannel)
       {
           if (msg == MENU_BACK)
           {
               gMenuPage--;
               show_dialog (id);
           }
           else if (msg == MENU_TOP)
           {
               gMenuPage = 0;
               show_dialog (id);
           }
           else if (msg == MENU_NEXT)
           {
               gMenuPage++;
               show_dialog (id);
           }            
           else
           {
               // should be something in the inventory to give out
               give_notecard (id, msg);
           }
       }
   }
   changed (integer change)
   {
       if (change & CHANGED_INVENTORY)
       {
           llResetScript();
       }
   }

} </lsl>

Dialog Texture Changer

<lsl> // Script to present a dialog populated with a list of textures // from the prims inventory. // // Note that only the first 24 characters of the texture names // are actually used - as only 24 chars can be placed in a dialog // button. This means that if two textures have the first 24 chars // the same, the second will be ignored ... // // Note however that usually, buttons don't display more than, say, // around 10 or so characters anyway!! // // Kimm Paulino // Aug 2011

integer gDebug = FALSE; integer gReshow = FALSE;

integer gMenuPage; integer gNumTextures; list gTextures;

integer gListenChannel; integer gListenHandle; float gListenTimer; float LISTEN_TIMEOUT=300.0;

string MENU_NEXT="Next>"; string MENU_TOP="Top"; string MENU_BACK="<Back"; string MENU_SPACE=" ";

doDebug (string msg) {

   if (gDebug)
   {
       llOwnerSay (msg);
   }

}

integer getRandomChannel () {

   // Based on http://tali.appspot.com/html/scripting/snippets.html
   // Always leaves 17th bit set (so never a number less than 65535)
   // Always leaves sign bit unset (so is always positive)
   integer pos_int = (((integer)llFrand(16384)) << 17) | 65536 | ((integer)llFrand(65535));
   return -pos_int;

}

read_textures () {

   gNumTextures = llGetInventoryNumber (INVENTORY_TEXTURE);
   gTextures = [];
   
   if (gNumTextures == 0)
   {
       llOwnerSay ("Note: There are no textures in this prim ...");
       return;
   }
   
   integer i;
   for (i=0; i<gNumTextures; i++)
   {
       string texture = llGetInventoryName (INVENTORY_TEXTURE, i);
       // Only store the first 24 characters of the name as this is all we can
       // show in a dialog anyway
       gTextures += [llGetSubString (texture, 0, 23)];
       doDebug ("read_textures: adding texture: " + texture);
   }

}

apply_texture (string texture) {

   integer idx = llListFindList (gTextures, [texture]);
   if (idx == -1)
   {
       // Didn't find it in the list - might not be a texture
       return;
   }
   // Now need to find the texture that matches from the inventory
   // and give it out
   integer i;
   for (i=0; i<gNumTextures ; i++)
   {
       string texture_full = llGetInventoryName(INVENTORY_TEXTURE, i);
       if (llGetSubString (texture_full, 0, 23) == texture)
       {
           // This is the one
           doDebug ("apply_texture: applying texture: " + texture_full);
           llSetTexture (texture_full, ALL_SIDES);
           return;
       }
   }    

}

show_dialog (key id) {

   list buttons = [];
   
   if (gNumTextures == 0)
   {
       do_dialog (id, "There are no textures to select at present.", []);
       return;
   }
   
   // If several people are using the giver, then it is possible
   // for gMenuPage to go negative or too high
   if (gMenuPage < 0)
   {
       // Reset ...
       gMenuPage = 0;
   }
   integer num_pages = ((gNumTextures+8)/9);
   if (gMenuPage >= num_pages)
   {
       // gMenuPage is an index starting at 0...
       // max is a 1...
       gMenuPage = num_pages-1;
   }
   
   // Note: This yields notecards counting as follows:
   // 0 to 8 = first page,
   // 9 to 17 = next page, etc
   integer first_texture = gMenuPage * 9;
   integer last_texture = first_texture + 9 - 1;
   if (last_texture >= gNumTextures)
   {
       // recall last_texture indexed from 0, but
       // gNumTextures is indexed from 1
       last_texture = gNumTextures-1;
   }
   if (num_pages > 1)
   {
       if (gMenuPage > 0)
       {
           buttons += [MENU_BACK];
       }
       else
       {
           buttons += [MENU_SPACE];
       }
       if (gMenuPage == 0)
       {
           buttons += [MENU_SPACE];
       }    
       else
       {
           buttons += [MENU_TOP];
       }
       // If not on the last page, and there are more pages to come ...
       if (gNumTextures > 9 && gMenuPage < (num_pages-1))
       {
           buttons += [MENU_NEXT];
       }
       else
       {
           buttons += [MENU_SPACE];
       }
   }
   integer i;
   for (i=first_texture; (i <= last_texture) && (i < gNumTextures); i++)
   {
       buttons += [llList2String (gTextures, i)];
   }
   do_dialog (id, "\n\n(click \"Ignore\" when done)", buttons);

}

do_dialog (key id, string msg, list buttons) {

   llListenRemove (gListenHandle);
   gListenChannel = getRandomChannel();
   gListenHandle = llListen (gListenChannel, "", id, "");
   llSetTimerEvent (LISTEN_TIMEOUT);
   llDialog (id, msg, buttons, gListenChannel);

}

default {

   on_rez (integer start_param)
   {
       llResetScript();
   }
   state_entry()
   {
       read_textures();
       gMenuPage = 0;
   }
   touch_start(integer total_number)
   {
       // Start menu system again. Note that I don't do anything special
       // with several people trying to touch it at the same time - it will
       // always overlap the processing of gMenuPage, but all that will
       // happen is that peoples next/back might be a bit quirky for a while.
       // Eventually, they will sort themselves out!
       gMenuPage = 0;
       show_dialog (llDetectedKey(0));
   }
   listen (integer channel, string name, key id, string msg)
   {
       if (channel == gListenChannel)
       {
           if (msg == MENU_BACK)
           {
               gMenuPage--;
               show_dialog (id);
           }
           else if (msg == MENU_TOP)
           {
               gMenuPage = 0;
               show_dialog (id);
           }
           else if (msg == MENU_NEXT)
           {
               gMenuPage++;
               show_dialog (id);
           }            
           else
           {
               // should be something in the inventory to give out
               apply_texture (msg);
               
               // reshow the dialog
               if (gReshow)
               {
                   show_dialog(id);
               }
           }
       }
   }
   timer ()
   {
       // First see if we are timing out on a listen event
       if (gListenHandle != 0)
       {
           // Remove the old listen
           llListenRemove (gListenHandle);
           gListenHandle = 0;
           gListenChannel = 0;
       }
   }
   changed (integer change)
   {
       if (change & CHANGED_INVENTORY)
       {
           llResetScript();
       }
   }

} </lsl>