Grab Slide Lever

= Description =
This sample script creates a slide lever which will move through a series of positions between two points as a user drags it about. This script is a part of Haravikk Mistral's Lever Library.
When dragged this lever will move in the direction it is dragged along its fixed axis until it reaches FORWARD_POINT, or BACKWARD_POINT. This will result in a lever which can be moved between these two limits simply by dragging it.
You should be wary when setting points when it comes to local and global co-ordinates. It is strongly recommended that you use only levers that are child-prims of something else, so that they may use local co-ordinates which are a lot easier to use. You can get the co-ordinates you require using the llGetLocalPos() function.
This script also contains other features, including the ability to have it reset after a period of time, and also have it contact other objects regarding changes, either using link-messages or by sending messages using whisper/say/shout/region-say. See the comments in the script below for COMMS_TYPE on how to enable these.
= Script =
<lsl>// =====================================================================
// Simple Touch Slide Lever by Haravikk Mistral
// =====================================================================
// This script allows you to create a simple lever that when touched 
// will slide through a set of positions. It is best used when the lever 
// prim is a child of a root prim, as the slide is performed between two 
// points.
//
// This script can be used and modified freely, though I ask that you 
// give credit to myself where possible, and provide a link to my 
// wiki page at https://wiki.secondlife.com/wiki/User:Haravikk_Mistral
// =====================================================================
 
// =====================================================================
// CONSTANTS
// =====================================================================
// The following values can be used to quickly set-up a lever without 
// having to modify the script. Read the comments above each item to 
// get an idea of what to do.
 
// The number of lever positions from FORWARD_POINT to BACKWARD_POINT 
// (inclusive). Minimum POSITIONS is 2.
integer POSITIONS = 3;
// The starting position, positions are numbered from 1 to POSITIONS
integer START_POSITION = 1;
// The starting direction, TRUE will slide the lever forward from it's 
// starting position, FALSE will slide it backward first. When it 
// reaches it's limit it will change direction.
integer START_DIRECTION = TRUE;
 
// The forward/end-point of the slide lever's movement
vector FORWARD_POINT = <-0.15, 0.0, 0.0>;
// The back/start-point of the slide lever's movement
vector BACKWARD_POINT = <0.15, 0.0, 0.0>;
 
// This is the amount the lever must be dragged by before it will move 
// to the next position.
float DRAG_SENSITIVITY = 0.5;
// The axis on which to test drag-amount, only one should be set to 1.0, 
// the rest to 0.0, unless you know what you're doing.
vector DRAG_AXIS = <-1.0, 0.0, 0.0>;
 
// A sound to play when moving the lever
key SOUND = NULL_KEY;
// The volume of the sound
float SOUND_VOLUME = 1.0;
 
// This value determines how long the lever will wait before resetting 
// it's position back to START_POSITION and START_DIRECTION. Set this 
// to 0.0 if you never want to reset the lever.
float RESET_TIME = 30.0;
 
// Determines what type of message this lever sends when its position 
// changes. The available types are:
// -1 lever sends no communications (lever is a prop only)
// 0 link-message (see LINK_* constants below to set-up)
// 1 whisper (see CHANNEL constant)
// 2 say (see CHANNEL constant)
// 3 shout (see CHANNEL constant)
// 4 region-say (see CHANNEL constant)
//
// Link messages will have the position number in their string parameter 
// and the key of the user who touched the lever in the key parameter.
// All other message types will contain a message in the form:
// <position>,<user>
// That is; the position, and user-key are comma-separated.
// NOTE: Any time the lever positions itself (reset) the key will be 
// NULL_KEY.
integer COMMS_TYPE = -1;
 
// Determines what channel the lever whispers/says/shouts/region-says on
integer CHANNEL = -1234567890;
 
// The link in a linked-set to send messages to upon changing position
integer LINK_TARGET = LINK_ROOT;
// The value to place in the integer parameter of a link-message for 
// easy filtering.
integer LINK_FILTER = 8192;
 
// =====================================================================
// VARIABLES
// =====================================================================
// Below here are variables, these are used by the script.
integer position = 0;
vector lastGrab = ZERO_VECTOR;
 
float positionsF = 0.0;
 
moveToPosition(integer newPosition, key id) {
 if (newPosition > POSITIONS) newPosition = POSITIONS;
 else if (newPosition < 1) newPosition = 1;
 
 integer steps = newPosition - position;
 if (!steps) return; // Already there
 
 integer change = 1;
 if (steps < 0) {
 change = -1;
 steps = -steps;
 }
 
 integer i = 0;
 vector diff = FORWARD_POINT - BACKWARD_POINT;
 
 do {
 position += change;
 llSetPos(BACKWARD_POINT + (
 diff * ((float)(position - 1) / positionsF))
 );
 
 if (SOUND) llPlaySound(SOUND, SOUND_VOLUME);
 } while ((++i) < steps);
 
 if (COMMS_TYPE >= 0) {
 if (COMMS_TYPE == 0)
 llMessageLinked(LINK_TARGET, LINK_FILTER, (string)position, id);
 else {
 string str = (string)position + "," + (string)id;
 
 if (COMMS_TYPE == 1) llWhisper(CHANNEL, str);
 else if (COMMS_TYPE == 2) llSay(CHANNEL, str);
 else if (COMMS_TYPE == 3) llShout(CHANNEL, str);
 else if (COMMS_TYPE == 4) llRegionSay(CHANNEL, str);
 }
 }
 
 if (RESET_TIME > 0.0) llSetTimerEvent(RESET_TIME);
}
 
default {
 state_entry() {
 positionsF = (float)(POSITIONS - 1);
 moveToPosition(START_POSITION, NULL_KEY);
 }
 
 touch(integer x) {
 vector grab = llDetectedGrab(0);
 vector diff = grab - lastGrab;
 diff.x *= DRAG_AXIS.x;
 diff.y *= DRAG_AXIS.y;
 diff.z *= DRAG_AXIS.z;
 float drag = llVecMag(diff);
 
 if (drag > DRAG_SENSITIVITY) {
 integer sign = TRUE;
 if (diff.x < 0.0) sign = !sign;
 if (diff.y < 0.0) sign = !sign;
 if (diff.z < 0.0) sign = !sign;
 
 integer steps = llFloor(drag / DRAG_SENSITIVITY);
 if (steps <= 0) return;
 
 integer newPosition = position;
 if (sign) newPosition += steps;
 else newPosition -= steps;
 
 if (newPosition > POSITIONS) newPosition = POSITIONS;
 else if (newPosition < 1) newPosition = 1;
 
 moveToPosition(newPosition, llDetectedKey(0));
 lastGrab = grab;
 }
 }
 
 touch_start(integer x) {
 lastGrab = ZERO_VECTOR;
 }
 
 touch_end(integer x) {
 lastGrab = ZERO_VECTOR;
 }
 
 timer() {
 llSetTimerEvent(0.0);
 moveToPosition(START_POSITION, NULL_KEY);
 }
}</lsl>