Touch Pivot Lever

= Description =
This sample script creates a pivot lever which will rotate around its centre-point through a series of positions, every time a user touches it. This script is a part of Haravikk Mistral's Lever Library.
When touched this lever will move in a given direction until it reaches FORWARD_LIMIT, at which point any subsequent touch will cause it to change direction, moving instead towards BACKWARD_LIMIT where the same behaviour occurs but in reverse. This will result in a lever which can be moved between these two limits simply by touching it.
There is however a special case. Should you set BACKWARD_LIMIT equal to FORWARD_LIMIT then the lever will enter a "continuous" mode whereby it will rotate in a single direction in a full-circle, as it essentially has no limit. In this case it will repeatedly move through the available positions, rather than alternating between them.
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 Pivot Lever by Haravikk Mistral
// =====================================================================
// This script allows you to create a simple lever that when touched 
// will rotate through a set of positions. It is intended to be used 
// with a lever prim that has it's cut values set so that it is sliced 
// in half, this allows it to rotate cleanly.
//
// 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_LIMIT and BACKWARD_LIMIT 
// (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 rotate the lever forward from it's 
// starting position, FALSE will rotate it backward first. When it 
// reaches it's limit it will change direction.
integer START_DIRECTION = TRUE;
// The angle (in degrees) that is as far forward as the lever will go.
float FORWARD_LIMIT = 300.0;
// The angle (in degrees) that is as far back as the lever will go. If 
// you set this equal to FORWARD_LIMIT then you will create a continuous 
// "wheel" type lever that can be rotated round and round as you please.
float BACKWARD_LIMIT = 240.0;
// The axis on which to rotate the lever, only one should be set to 1.0, 
// the rest to 0.0, unless you know what you're doing.
vector ROTATE_AXIS = <0.0, 1.0, 0.0>;
// This is how long it takes to rotate the lever to it's next position.
float ROTATE_TIME = 0.5;
// 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;
integer direction = START_DIRECTION;
float positionsF = 0.0;
integer continuous = FALSE;
rotateToPosition(integer newPosition, key id) {
 if (newPosition > POSITIONS) newPosition = POSITIONS;
 else if (newPosition < 1) newPosition = 1;
 
 integer steps = newPosition - position;
 if (!steps) return; // Already there
 
 if (!continuous) direction = (steps > 0);
 
 integer change = 1;
 if (steps < 0) {
 change = -1;
 steps = -steps;
 }
 
 integer i = 0; float angle = 0.0;
 rotation rotOffset = ZERO_ROTATION;
 integer doOffset = (llGetLinkNumber() > 1);
 rotation rot;
 
 float strength = (llGetMass() / 2.0) / ROTATE_TIME;
 
 do {
 position += change;
 angle = BACKWARD_LIMIT + ((FORWARD_LIMIT - BACKWARD_LIMIT) * 
 ((float)(position - 1) / positionsF));
 
 rot = llEuler2Rot(ROTATE_AXIS * angle);
 if (doOffset) rot /= rotOffset;
 
 llRotLookAt(
 rot,
 strength,
 1.0
 );
 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);
 }
 }
 
 llSleep(ROTATE_TIME);
 llStopLookAt();
 
 if (RESET_TIME > 0.0) llSetTimerEvent(RESET_TIME);
}
default {
 state_entry() {
 // Determine if the lever is continuous (can turn full-circle) or 
 // fixed (turns on part of a circle)
 continuous = (FORWARD_LIMIT == BACKWARD_LIMIT);
 
 // Convert angles to radians
 FORWARD_LIMIT *= DEG_TO_RAD;
 if (continuous) BACKWARD_LIMIT = FORWARD_LIMIT + TWO_PI;
 else BACKWARD_LIMIT *= DEG_TO_RAD;
 
 // Convert POSITIONS to a float for rotations
 positionsF = (float)(POSITIONS - 1);
 
 rotateToPosition(START_POSITION, NULL_KEY);
 }
 
 touch_start(integer x) {
 x = position;
 if (direction) {
 if ((++x) > POSITIONS) {
 if (continuous) x = 2;
 else x = POSITIONS - 1;
 }
 } else if ((--x) < 1) {
 if (continuous) x = POSITIONS - 1;
 else x = 2;
 }
 
 rotateToPosition(x, llDetectedKey(0));
 }
 
 timer() {
 llSetTimerEvent(0.0);
 rotateToPosition(START_POSITION, NULL_KEY);
 direction = START_DIRECTION;
 }
}</lsl>