User:Kimm Paulino/Scripts

From Second Life Wiki
< User:Kimm Paulino
Revision as of 13:13, 18 May 2016 by Kimm Paulino (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Simple Teleport

// 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);
            }
        }
    }
}

Die after 10 minutes

// 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();
    }
}


Simple Prim Texture Changer

// 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);
        if (texture != "")
        {
            llSetTexture(texture, ALL_SIDES);
        }
    }
    
    changed (integer change)
    {
        if (change & CHANGED_INVENTORY)
        {
            llResetScript();
        }
    }
}

Floating Script

// 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();
    }
}

Sit and Play Animation

// 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);
        }
    }
}

Move up and down

// 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 ...
//
// Update: Jan 2012
//  Add correction factor to correct for but SVC2441, when an
//  object will descend too fast.
//
//  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;
float DOWN_CORRECTION=1.5;  // Use trial-and-error to correct for SVC-2441
 
// Move to 2m above starting position
vector gOffset = <0.0, 0.0, 2.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;
            // Need to compensate (a bit) for SVC-2441
            llMoveToTarget(gStartPosition, MOVE_DAMPING*DOWN_CORRECTION);
            gState = DOWN;
        }
    }
}

Simple Door

// 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
    }
}

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.

// 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);
    }
}

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.

// 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);
    }
}

Simple Path Script

// 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();
    }
}

Visitor Counter and LM Giver

// Simple visitor counter and lm giver script.
//
// Can also run as a simple 'add me to the waiting list' script
// too (i.e. remembers everyone who touches the object).
//
// 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
// Updated for waiting list, Mar 2012

// Configure the behaviour we want
integer gWaitingList = FALSE;      // enable waiting list only functionality
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); 
        
        if (!gWaitingList)
        {
            // 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 num_detected)
    {
        integer i;
        for (i=0 ; i<num_detected ; i++)
        {
            key avid = llDetectedKey(i);
            if (avid == llGetOwner())
            {
                llListenRemove (gListenHandle); // in case things didn't complete last time
                llSetTimerEvent (600.0);  // In 10 minutes, all times out
                gListenHandle = llListen (gChannel, "", "", "");
                llDialog (avid, "Kimm's Visitor Tracker", ["List", "Reset"], gChannel);
            }
            else if (gWaitingList)
            {
                string av = llKey2Name (avid);
                addVisitor (av);
                llInstantMessage (avid, "Thank you, " + av + ", you have been added to the waiting list.");
            }
            else
            {
                // get the name of the first landmark in the inventory
                string lm = llGetInventoryName(INVENTORY_LANDMARK, 0);
                if (lm != "")
                {
                    llGiveInventory(avid, 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);
    }
}

LM, Notecard, Group, URL giver

// 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();
        }
    }
}

HTML Page from a notecard on a prim

// 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><h1>HTML on a Prim Example</h1><p align=\"centre\">Kimm Paulino, Jan 2011</p></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);
            }
        }
    }
}

Multiple Notecard Giver

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

// 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();
        }
    }
}

Dialog Texture Changer

// Script to present a dialog populated with a list of textures
// either from the prims inventory or specified by UUID in a notecard.
//
// Notecard lines have the form:
//    Texture Name=UUID
//
// 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!!
//
// There are two methods of setting textures - one (gSetTexture = TRUE)
// allows you to use UUIDs, etc, but the other (gSetTexture = FALSE)
// will allow you to specify the rotation and offset values.
//
// There is also an option to just use the values from the texture already
// on the face (but this won't work for ALL_SIDES).
//
// Both allow you to set the face.
//
// Kimm Paulino
// Feb 2012

// Controls how the script operates:
integer gReshow = FALSE;
integer gSetTexture = FALSE;               // Use SetTexture method (needed for textures not in the prim inventory)
integer gNotecards = FALSE;               // Get textures to use from a notecard not inventory
integer gPreserveTextures = FALSE;      // Read offset/rotation values from existing texture

// configures how textures appear
integer gFace = ALL_SIDES;
vector  gRepeats = <1.0, 1.0, 1.0>;
vector  gOffsets = <1.0, 1.0, 1.0>;
float     gRotationInDegrees = 0.0;

// Do not change anything below this line (unless you know what you are doing)
integer gDebug = FALSE;
integer gMenuPage;
integer gNumTextures;
list gTextures;
list gTextureUUIDs;

string gNotecardName;
integer gNotecardLine = 0;
key gQueryID;

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;
}

integer read_textures_from_inventory ()
{
    gNumTextures = llGetInventoryNumber (INVENTORY_TEXTURE);
    gTextures = [];
    
    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);
    }
    
    return gNumTextures;
}

integer read_textures_from_notecards ()
{
    // Read texture information from the first notecard in inventory
    gNotecardName = llGetInventoryName(INVENTORY_NOTECARD, 0);
    if (gNotecardName == "")
    {
        return FALSE;
    }
    else
    {
        gQueryID = llGetNotecardLine(gNotecardName, gNotecardLine);
        return TRUE;
    }
}

set_texture (string texture)
{
    if (gSetTexture)
    {
        llSetTexture (texture, gFace);
    }
    else if (gPreserveTextures && gFace != ALL_SIDES)
    {
        // returns: [string texture, vector scale, vector offset, float rotation]
        list scale_offset_rot = llGetPrimitiveParams ([PRIM_TEXTURE, gFace]);

        llSetPrimitiveParams([PRIM_TEXTURE,
                                    gFace,
                                    texture,
                                    llList2Vector (scale_offset_rot, 1),
                                    llList2Vector (scale_offset_rot, 2),
                                    llList2Float   (scale_offset_rot, 3)]
                                    );
    }
    else
    {
        llSetPrimitiveParams([PRIM_TEXTURE,
                                    gFace,
                                    texture,
                                    gRepeats,
                                    gOffsets,
                                    gRotationInDegrees*DEG_TO_RAD]
                                    );
    }
}

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++)
    {
        if (gNotecards)
        {
            string texture_uuid = llList2Key (gTextureUUIDs, idx);
            if (texture_uuid != "")
            {
                doDebug ("apply_texture: applying texture: " + texture_uuid);
                set_texture (texture_uuid);
                return;
            }
        }
        else
        {
            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);
                set_texture (texture_full);
                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;
    }
    
    // Max buttons on a dialog is 12, so if more than that, then need to 
    // include special next/back button handling
    integer first_texture = 0;
    integer last_texture = gNumTextures-1;
    if (gNumTextures > 12)
    {
        // Note: This yields notecards counting as follows:
        // 0 to 8 = first page,
        // 9 to 17 = next page, etc
        first_texture = gMenuPage * 9;
        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 (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()
    {
        if (read_textures_from_inventory())
        {
            // We have textures in the inventory and have read them
            gNotecards = FALSE;
        }
        else if (read_textures_from_notecards())
        {
            // We have textures read in from a notecard by UUID
            // (or will have pretty soon)
            gNotecards = TRUE;
        }
        else
        {
            llOwnerSay ("Note: There are no textures or notecards in this prim ...");
        }
        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;
        }
    }

    dataserver(key query_id, string line)
    {
        if (query_id == gQueryID)
        {
            if (line != EOF)
            {
                if (llSubStringIndex (line, "=") != 0)
                {
                    list vals = llParseString2List (line, ["="], []);
                    
                    // Only store the first 24 characters of the name as this is all we can
                    // show in a dialog anyway
                    string texture = llList2String (vals, 0);
                    string uuid = llList2Key (vals, 1);
                    gTextures += [llGetSubString (texture, 0, 23)];
                    gTextureUUIDs += uuid;
                    doDebug ("Adding: " + texture + " ("+uuid+")");
                }
                
                // Now get the next line
                gNotecardLine++;
                gQueryID = llGetNotecardLine(gNotecardName, gNotecardLine);
            }
            else
            {
                gNumTextures = llGetListLength (gTextures);
                doDebug ("Found " + (string)gNumTextures + " textures.");
            }
        }
    }

    changed (integer change)
    {
        if (change & CHANGED_INVENTORY)
        {
            llResetScript();
        }
    }
}

Simple Prim Lighting

// Simple lighting (prim glow) script
//
// Lighting toggled on touch.
//
// For a version of this script that includes remote switching
// and multi-prim switching functionality, see:
// http://kimmscripts.wordpress.com/2011/11/23/simple-remote-lighting/
//
// Kimm Paulino
// January 2012

// Properties of the light you may wish to change
// ----------------------------------------------------
// LIGHT = TRUE or FALSE - turns the light on or off (lights contribute to lag in SL)
integer LIGHT = TRUE;

// BRIGHT = TRUE or FALSE - turns brightness on or off (less laggy than lights)
integer BRIGHT = TRUE;

// GLOW = Range 0.0 to 1.0 - dim to full glow
float GLOW = 1.0;

// See: http://lslwiki.net/lslwiki/wakka.php?wakka=color
vector COLOUR = <1.0, 1.0, 1.0>;

// Intensity of the radiated light - range 0.0 to 1.0
float INTENSITY = 1.0;    

// Radius of the radiated light (in metres) - range 0.1 to 20.0
float RADIUS = 5.0;

// Rate light decays - range 0.01 to 2.0 (no decay to quick decay)
float FALLOFF = 0.5;

// See: http://wiki.secondlife.com/wiki/Face
integer FACE = ALL_SIDES;
// ----------------------------------------------------

integer gOn = FALSE;

on ()
{
    // Turn on to the required settings
    llSetPrimitiveParams([
            PRIM_POINT_LIGHT, LIGHT, COLOUR, INTENSITY, RADIUS, FALLOFF,
            PRIM_FULLBRIGHT, FACE, BRIGHT,
            PRIM_GLOW, FACE, GLOW
            ]);
}

off ()
{
    llSetPrimitiveParams([
            PRIM_POINT_LIGHT, FALSE, ZERO_VECTOR, 0.0, 0.0, 0.0,
            PRIM_FULLBRIGHT, FACE, FALSE,
            PRIM_GLOW, FACE, FALSE
            ]);
}

performLightCmd ()
{
    if (gOn)
    {
        off();
        gOn = FALSE;
    }
    else
    {
        on();
        gOn = TRUE;
    }
}

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

    state_entry ()
    {
        gOn = FALSE;
    }
    
    touch_start (integer num_detected)
    {
        performLightCmd();
    }
}

Tint on Touch

// Simple opacity level changing script.
// Script cycles through the values on touch.
//
// Kimm Paulino
// Written for Tanisha Benoir, Feb 2012
 
integer gLowestLevel = 0;    // This is the most transparent setting (0, 1, 2, etc)
integer gSteps = 4;          // This is the number of steps to support

// Do not change below here
integer gOpacityLevel = 0;  

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

    touch_start(integer num_detected)
    {
        gOpacityLevel++;
        if (gOpacityLevel > gSteps)
        {
            gOpacityLevel = gLowestLevel;
        }
        float opacity = (1.0/(float)gSteps) * gOpacityLevel;
        llSetAlpha(opacity, ALL_SIDES);
    }
}

Notify Owner on Touch

// Send an IM using the text in helpmsg to the objects owner on touch.
// Will also send an acknowledgement IM using the text in ackmsg to the touching av.
//
// Optionally, if a notecard exists in the prim, it will also
// send that to the owner, as an extra, more 'in your face' prompt.
//
// Kimm Paulino
// Written for Synonyme Toll, Aug 2011

string helpmsg = "Someone needs help!";
string ackmsg = "I've IM'd Syn, the store owner.  If she doesn't materialise in a puff of smoke in the next few minutes, "
    + "well, I guess she is offline or afk, but I'm sure she will get back to you as soon as she can.  Do feel free to "
    + "continue browsing and thank you for stopping by the home of Synz Creations - http://synzcreations.wordpress.com/";

default
{
    touch_start (integer num_detected)
    {
        key owner = llGetOwner();
        key sender = llDetectedKey(0);
        string name = llKey2Name (sender);
        
        list timestamp = llParseString2List(llGetTimestamp(),["T","."],[""]);
        string time = llList2String(timestamp, 0) + " " + llList2String(timestamp, 1);
        
        llInstantMessage (sender, ackmsg);
        llInstantMessage (owner, time + " " + helpmsg + " [" + name + "]");
        
        // as an extra prompt, if a notecard is stored in the prim, send that
        // to the owner too ...
        string nc = llGetInventoryName(INVENTORY_NOTECARD, 0);

        if (nc != "")
            llGiveInventory(owner, nc); 
    }
}

Touch to disappear

// Touch to make an object disappear and reappear.
//
// Kimm Paulino
// Sept 2011
// Jan 2013: Update to affect entire linkset

integer gGone = FALSE;

default
{
    state_entry()
    {
        gGone = FALSE;
    }

    touch_start(integer total_number)
    {
        if (gGone)
        {
            llSetLinkAlpha (LINK_SET, 1.0, ALL_SIDES);
            gGone = FALSE;
        }
        else
        {
            llSetLinkAlpha (LINK_SET, 0.0, ALL_SIDES);
            gGone = TRUE;
        }
    }
}

Move up and Down and rotate

// Makes an object go up or down, gradually,
// with a slow rotation.
//
// Includes option for touch control.
//
// Warning:
//  * The animation will go a little weird if it is taken into inventory
//     whilst moving.
//  * It can't be used with a rezzer unless it is stopped (so
//     touch control is mandatory to turn it back on).
//
// Thanks to the members of the Advanced Scripters of SL group.
//
// Update Jan 2013: Added touch.
//
//  Kimm Paulino
//  February 2012
//

float gMoveTime = 4.0;                // Time for each movement
vector gOffset  = <0.0, 0.0, 2.0>;    // Where to move to (and back from)
vector gDegRot  = <0.0, 0.0, 180.0>;  // rotation to apply going up, then going down again 
integer gTouchControl = TRUE;           // FALSE to disable touch

integer gState;

default
{
    state_entry ()
    {
        gState = 0;
        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
    }
    
    touch_start (integer num_detected)
    {
        if (gTouchControl)
        {
            // 0 = first time; 1 = off; 2 = on
            if (gState == 0)
            {
                gState = 2;
                llSetKeyframedMotion (
                    [
                        gOffset, llEuler2Rot (gDegRot*DEG_TO_RAD), gMoveTime,
                        -gOffset,  llEuler2Rot (gDegRot*DEG_TO_RAD), gMoveTime
                    ],
                    [KFM_MODE, KFM_LOOP]);
            }
            else if (gState == 1)
            {
                // turn on
                gState = 2;
                llSetKeyframedMotion ([], [KFM_COMMAND, KFM_CMD_PLAY]);
            }
            else
            {
                // turn off
                gState = 1;
                llSetKeyframedMotion ([], [KFM_COMMAND, KFM_CMD_PAUSE]);
            }
        }
    }
 
    on_rez (integer n)
    {
        llResetScript();
    }
}

Orbit Maintaining Face Orientation

// Script to perform orbit whilst maintaining the same face
// to a specific direction.  This is designed for use with two linked
// prims, the root is the centre prim, and a second prim orbits around
// the root, always keeping its face in the same (global) direction.
//
// There are two ways of using this script - as a single script in
// the root prim, or with copies of the script in both root and any
// rotating child prims.
//
// Set gScriptScriptVersion = TRUE/FALSE as required
//
// The single script version is for placing in the central root
// prim and it will make the 1st linked prim orbit it.
//
// The multi-script version will need the script in the root (centre)
// prim and in any child prims that you wish to be orbiting.
//
// Kimm Paulino
// April 2012

// Single or multiple script version
integer gSingleScriptVersion = FALSE;

// Configure the script
integer gTouchControl = FALSE;
float   ROT_PERIOD = 5.0;
integer ROT_STEPS = 20;

// Don't change below here
integer gStep = 0;
integer gRotating = FALSE;
integer gRoot = FALSE;

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

    state_entry()
    {
        gStep = 0;
        integer num = llGetLinkNumber();
        if (num <= 1)
        {
            gRoot = TRUE;
        }
        
        if (gSingleScriptVersion && !gRoot)
        {
            // In single script version, only function if we are the root
            llOwnerSay ("This script is configured for Single Script use and so must be in the root (centre) prim.");
            return;
        }
        
        if (!gTouchControl)
        {
            llSetTimerEvent (ROT_PERIOD/(float)ROT_STEPS);
        }
    }
    
    touch_start (integer num_detected)
    {
        if (gSingleScriptVersion && !gRoot)
        {
            return;
        }

        if (gTouchControl)
        {
            if (gRotating)
            {
                gRotating = FALSE;
                llSetTimerEvent (0.0);
            }
            else
            {
                gRotating = TRUE;
                llSetTimerEvent (ROT_PERIOD/(float)ROT_STEPS);
            }
        }
    }
    
    timer ()
    {
        if (gSingleScriptVersion && !gRoot)
        {
            return;
        }

        gStep ++;
        if (gStep > ROT_STEPS)
        {
            gStep = 0;
        }
        
        // If this is the root, it is the centre prim
        
        // If single script, then move the slave then the root together
        if (gSingleScriptVersion)
        {
            // Now move the first linked prim
            float rot = - gStep * (360.0/(float)ROT_STEPS);
            llSetLinkPrimitiveParamsFast (2, [PRIM_ROTATION, llEuler2Rot (<0.0, 0.0, rot>*DEG_TO_RAD) / llGetRootRotation()]);

            // Move the root
            rot = gStep * (360.0/(float)ROT_STEPS);
            llSetRot (llEuler2Rot (<0.0, 0.0, rot>*DEG_TO_RAD));       
        }
        else // multiple scripts - just move one ...
        {
            if (gRoot)
            {
                // Move the root
                float rot = gStep * (360.0/(float)ROT_STEPS);
                llSetRot (llEuler2Rot (<0.0, 0.0, rot>*DEG_TO_RAD));       
            }
            else
            {
                // Move this prim, as a child
                float rot = - gStep * (360.0/(float)ROT_STEPS);
                llSetRot (llEuler2Rot (<0.0, 0.0, rot>*DEG_TO_RAD) / llGetRootRotation());       
            }
        }
    }
}

Notify List of Avatars

// Send an IM to the objects owner and the list of avs listed in the script.
// Will also send an acknowledgement IM to the touching av.
//
// NB that SL includes delays and throttling to prevent spamming
// see: https://wiki.secondlife.com/wiki/LlInstantMessage
//
// Kimm Paulino
// Written for Laz Longfall, Jun 2012

// List of full avatar UUIDs.
// you can get this from viewing the profile of each avatar in SL
// (well in firestorm anyway)
//
// IMPORTANT: There is no error checking of these - make
// sure you paste them in correctly!
list avs = [
"12345678-f00d-cafe-9876-0987654321ef",
"87654321-dead-dead-1234-fedcba012345"
// Remember no comma after the last entry ...
];
string helpmsg = "Someone needs help!";
string ackmsg = "I've IM'd the store owners.  Someone will be along as soon as possible.";

default
{
    touch_start (integer num_detected)
    {
        key owner = llGetOwner();
        key sender = llDetectedKey(0);
        string name = llKey2Name (sender);
        
        list timestamp = llParseString2List(llGetTimestamp(),["T","."],[""]);
        string time = llList2String(timestamp, 0) + " " + llList2String(timestamp, 1);
        
        llInstantMessage (sender, ackmsg);
        string msg = time + " " + helpmsg + " [" + name + "]";
        llInstantMessage (owner, msg);
        integer num = llGetListLength (avs);
        integer i;
        for (i=0; i<num ; i++)
        {
            // It would be nice to check that the UUIDs provided are for
            // real avs but llKey2Name needs and avatar to be present
            // and llRequestAgentData makes it all asynchronous
            // and will only return for the valid ids - there is no
            // dataserver event (as far as I can tell) for invalid ones, so
            // I'd have to have timeouts, etc, to check them.  Now I
            // could go through all that on startup to verify the UUIDs,
            // but this all seems like a significant complication in a
            // an otherwise really simple script ...
            // So just type the UUIDs correctly :)
            key uuid = llList2Key (avs, i);
            if (uuid == owner)
            {
                // we've already done this one
            }
            else
            {
                //llOwnerSay ("Messaging: " + uuid);
                llInstantMessage (uuid, msg);
            }
        }
    }
}

Simple Shower Particle Script

// Simple shower script.
// If you want the "droplet" texture, give me a shout (IM in-world)
//
// Particle Template from: http://lslwiki.net/lslwiki/wakka.php?wakka=LibraryKeknehvParticles
//
// Kimm Paulino
// March 2012

integer gState = 0;

StartWaterBurst ()
{
    llParticleSystem([                   //KPSv1.0  
        PSYS_PART_FLAGS , 0 //Comment out any of the following masks to deactivate them
    //| PSYS_PART_BOUNCE_MASK           //Bounce on object's z-axis
    //| PSYS_PART_WIND_MASK             //Particles are moved by wind
    | PSYS_PART_INTERP_COLOR_MASK       //Colors fade from start to end
    | PSYS_PART_INTERP_SCALE_MASK       //Scale fades from beginning to end
    //| PSYS_PART_FOLLOW_SRC_MASK         //Particles follow the emitter
    //| PSYS_PART_FOLLOW_VELOCITY_MASK    //Particles are created at the velocity of the emitter
    //| PSYS_PART_TARGET_POS_MASK       //Particles follow the target
    | PSYS_PART_EMISSIVE_MASK           //Particles are self-lit (glow)
    //| PSYS_PART_TARGET_LINEAR_MASK    //Undocumented--Sends particles in straight line?
    ,
    
    //PSYS_SRC_TARGET_KEY , NULL_KEY,   //Key of the target for the particles to head towards
                                                //This one is particularly finicky, so be careful.
    //Choose one of these as a pattern:
    //PSYS_SRC_PATTERN_DROP                 Particles start at emitter with no velocity
    //PSYS_SRC_PATTERN_EXPLODE              Particles explode from the emitter
    //PSYS_SRC_PATTERN_ANGLE                Particles are emitted in a 2-D angle
    //PSYS_SRC_PATTERN_ANGLE_CONE           Particles are emitted in a 3-D cone
    //PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY     Particles are emitted everywhere except for a 3-D cone
    
    PSYS_SRC_PATTERN,           PSYS_SRC_PATTERN_ANGLE_CONE
    
    ,PSYS_SRC_TEXTURE, "droplet"
    //,PSYS_SRC_TEXTURE,           ""                 //UUID of the desired particle texture, or inventory name
    ,PSYS_SRC_MAX_AGE,           0.0                //Time, in seconds, for particles to be emitted. 0 = forever
    ,PSYS_PART_MAX_AGE,          2.0                //Lifetime, in seconds, that a particle lasts
    ,PSYS_SRC_BURST_RATE,        0.0                //How long, in seconds, between each emission
    ,PSYS_SRC_BURST_PART_COUNT,  10                  //Number of particles per emission
    ,PSYS_SRC_BURST_RADIUS,      0.0                //Radius of emission
    ,PSYS_SRC_BURST_SPEED_MIN,   1.0                //Minimum speed of an emitted particle
    ,PSYS_SRC_BURST_SPEED_MAX,   2.0               //Maximum speed of an emitted particle
    ,PSYS_SRC_ACCEL,             <0.0,0.0,-2.0>     //Acceleration of particles each second
    ,PSYS_PART_START_COLOR,      <0.9,0.9,1.0>      //Starting RGB color
    ,PSYS_PART_END_COLOR,        <0.7,0.7,1.0>      //Ending RGB color, if INTERP_COLOR_MASK is on 
    ,PSYS_PART_START_ALPHA,      0.7                //Starting transparency, 1 is opaque, 0 is transparent.
    ,PSYS_PART_END_ALPHA,        0.5                //Ending transparency
    ,PSYS_PART_START_SCALE,      <0.05,0.05,0.0>      //Starting particle size
    ,PSYS_PART_END_SCALE,        <0.12,0.12,0.0>      //Ending particle size, if INTERP_SCALE_MASK is on
    ,PSYS_SRC_ANGLE_BEGIN,       0                 //Inner angle for ANGLE patterns
    ,PSYS_SRC_ANGLE_END,         PI / 6.0           //Outer angle for ANGLE patterns
    ,PSYS_SRC_OMEGA,             <0.0,0.0,0.0>      //Rotation of ANGLE patterns, similar to llTargetOmega()
            ]);
}

WaterStop()
{
    llParticleSystem ([]);
}


default
{    
    state_entry()
    {
        gState = 0;
    }

    touch_start( integer num )
    {
        gState++;
        if (gState > 1)
        {
            gState = 0;
        }
        
        if (gState == 1)
        {
            StartWaterBurst();
        }
        else
        {
            WaterStop();
        }
    }
}