Differences

This shows you the differences between two versions of the page.


Previous revision
Next revision
fuss:lsl [2019/11/12 16:46] – [Ternary Operator] office
Line 1: Line 1:
 +====== Copyright ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3      //
 +//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
 +//  rights of fair usage, the disclaimer and warranty conditions.        //
 +///////////////////////////////////////////////////////////////////////////
 +</code>
 +
 +
 +====== Empty List ======
 +
 +Instead of:
 +<code lsl2>
 +if(!llGetListLength(l))) {
 +  llOwnerSay("not empty");
 +}
 +</code>
 +you can use:
 +<code lsl2>
 +default
 +{
 +    state_entry()
 +    {
 +        list l = [ 0 ];
 +        if(l) {
 +            llOwnerSay("not empty");
 +            return;
 +        }
 +        llOwnerSay("empty");
 +    }
 +}
 +</code>
 +
 +For:
 +<code lsl2>
 +list l = [ 0 ];
 +</code>
 +**Output:**
 +<code>
 +Object: not empty
 +</code>
 +For:
 +<code lsl2>
 +list l = [];
 +</code>
 +**Output:**
 +<code>
 +Object: empty
 +</code>
 +
 +The same applies for:
 +
 +  * vectors, ''ZERO_VECTOR'' is equivalent to false.
 +  * strings, the empty string is equivalent to false
 +
 +However, all of them must be alone in the ''if''-block. For example:
 +
 +<code lsl2>
 +list a = [];
 +if(a) {
 +  // not empty
 +}
 +// empty
 +</code>
 +will work as explained earlier.
 +
 +But:
 +
 +<code lsl2>
 +list a = [];
 +if(a && TRUE) {
 +  // a not empty
 +}
 +// empty
 +</code>
 +will not compile and will issue a type mismatch error.
 +
 +====== 3-Term Logical Disjunction Using Vectors ======
 +
 +If you have three flags and you wish to create a conditional based on whether at least one of them is true, consider placing them in a vector primitive and making the vector the conditional.
 +
 +**Example**
 +<code lsl2>
 +vector true = <TRUE,FALSE,FALSE>; // = <TRUE,TRUE,FALSE>, <TRUE,TRUE,TRUE>, <FALSE,TRUE,TRUE>, <FALSE,FALSE,TRUE>
 +vector false = <FALSE,FALSE,FALSE>;
 +
 +        if(true) { // Same as if(<TRUE,FALSE,FALSE>) { }
 +            llSay(0, "PASS");
 +            return;
 +        }
 +        llSay(0, "FAIL");
 +</code>
 +
 +**Usage**
 +<code lsl2>
 +integer flag_a = 0;
 +integer flag_b = 1;
 +integer flag_c = 1;
 +
 +        if(<flag_a, flag_b, flag_c>) {
 +            llSay(0, "PASS");
 +            return;
 +        }
 +        llSay(0, "FAIL");
 +</code>
 +
 +====== Conditional Re-entry Flipping ======
 +
 +An elegant way to alternate between ''timer'' re-entries (or any re-entrant function) and makes a heck of a smiley.
 +
 +**Globals**
 +<code lsl2>
 +integer o = -1;
 +</code>
 +**Inline usage**
 +<code lsl2>
 +    timer() {
 +        if(o = ~o) {
 +            /* Second timer() entry,fourth timer() entry,...,n+1th timer() entry. */
 +            return;
 +        }
 +        /* First timer() entry, third timer() entry,...,nth timer() entry. */
 +    }
 +</code>
 +
 +====== ASCII Progress Bar ======
 +
 +**Globals**
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasProgress(integer percent, integer length, list symbols) {
 +    percent /= (integer)((float)100.0/(length));
 +    string p = llList2String(symbols,0);
 +    integer itra = 0;
 +    do {
 +        if(itra>percent-1) p += llList2String(symbols,2);
 +        else p += llList2String(symbols,1);
 +    } while(++itra<length);
 +    return p + llList2String(symbols,3);
 +}
 +</code>
 +
 +This function will return a string in this format:
 +<code>
 +symbols(0) + symbols(1) percent of length times + symbols(2) till 100 percent of length + symbols(3)
 +</code>
 +
 +In other words, given a call:
 +<code lsl2>
 +progress(50, 10, ["[", "#", "o", "]"]);
 +</code>
 +
 +it will return the string:
 +
 +<code>
 +[#####ooooo]
 +</code>
 +
 +This might come in handy together with ''llSetText'' and will allow you to display a progress bar, eg:
 +<code lsl2>
 +llSetText("Progress:\n" + wasProgress(50, 10, ["[", "#", "o", "]"]), <1,1,1>, 1.0);
 +</code>
 +
 +====== Generate Group-Invite Links ======
 +
 +Unless using bots, the only way to invite somebody to a group is to send them a link using the <code>secondlife:///</code> protocol which would make the viewer open the group profile window.
 +
 +**Inline Usage**
 +<code lsl2>
 +llInstantMessage(id, /* Message to the user, eg: to click on the link and press join. */ + 
 +    "\n secondlife:///app/group/" + (string)/* Key of the group */ + "/about")
 +</code>
 +
 +
 +
 +====== Generic Notecard Reader ======
 +
 +This is a generic notecard loader. In this variation, it does not set a timer to check whether the notecard has been loaded. Thus, all the notecards should have an ending newline for this system to work properly.
 +
 +**Globals**
 +<code lsl2>
 +key nQuery = NULL_KEY;
 +integer nLine = 0;
 +list nList = [];
 +//pragma inline
 +string nName = "New Notecard";
 +</code>
 +**Reading**
 +<code lsl2>
 +        if(llGetInventoryType(nName) != INVENTORY_NOTECARD) {
 +            llSay(DEBUG_CHANNEL, "Failed to find a notecard named " + nName + " in the primitive's inventory.");
 +            return;
 +        }
 +        nQuery = llGetNotecardLine(nName, nLine);
 +</code>
 +**Dataserver**
 +<code lsl2>
 +    dataserver(key id, string data) {
 +        if(id != nQuery) return;
 +        if(data == EOF) return;
 +        if(data == "") jump continue;
 +        nList += data;
 +@continue;
 +        nQuery = llGetNotecardLine(nName, ++nLine);
 +    }
 +</code>
 +**Extension: Return if key/name NOT in nList**
 +<code lsl2>
 +        if(llListFindList(nList, (list)/* llDetectedKey(0), llDetectedName(0) */) == -1) return;
 +</code>
 +
 +====== Side-By-Side Dual List Enumeration ======
 +
 +Enumerates two lists side-by side, separating the elements by a separator.
 +
 +**Globals**
 +<code lsl2>
 +list list_a = [ /* contents */ ];
 +list lib_b = [ /* contents */ ];
 +</code>
 +**Inline usage**
 +<code lsl2>
 +            llOwnerSay("--------------- START DUMP ---------------");
 +            for(itra=0; itra<llGetListLength(list_a); ++itra) {
 +                llOwnerSay(llList2String(list_a, itra) + " /* separator */ " + llList2String(list_b, itra));
 +                llSleep(llGetRegionTimeDilation());
 +            }
 +            llOwnerSay("---------------- END DUMP ----------------");
 +</code>
 +
 +====== Map Preserving Sort using Quicksort for Strings ======
 +
 +Although ''llListSort'' sorts individual lists, there may be cases where you would want to sort two lists so that their mappings remain stable.
 +
 +More precisely, in the example below, we have a map of letters to numbers. The first column represents the contents of list_a and the right column represents the contents of list_b:
 +<code>
 +f -> 10
 +a -> 37
 +d -> 1
 +e -> 4
 +b -> 2
 +c -> 3
 +z -> 1
 +</code>
 +
 +and we want to sort list_a lexicographically while preserving the mapping from letters to numbers above. The expected result should be:
 +
 +<code>
 +a -> 37
 +b -> 2
 +c -> 3
 +d -> 1
 +e -> 4
 +f -> 10
 +z -> 1
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer stringComparer(string a, string b) {
 +    list alph = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", 
 +                      "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" ];
 +    return llListFindList(alph, (list)a) <= llListFindList(alph, (list)b);
 +}
 +
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasDualQuicksort(list a, list b) {
 +    if(llGetListLength(a) <= 1) return a+b;
 + 
 +    list pivot_a = llList2List(a, 0, 0);
 +    a = llDeleteSubList(a, 0, 0);
 +    list pivot_b = llList2List(b, 0, 0);
 +    b = llDeleteSubList(b, 0, 0);
 + 
 +    list less = [];
 +    list less_b = [];
 +    list more = [];
 +    list more_b = [];
 + 
 +    do {
 +        if(stringComparer(llList2String(a, 0), llList2String(pivot_a, 0)) == TRUE) {
 +            less += llList2List(a, 0, 0);
 +            less_b += llList2List(b, 0, 0);
 +            jump continue;
 +        }
 +        more += llList2List(a, 0, 0);
 +        more_b += llList2List(b, 0, 0);
 +@continue;
 +        a = llDeleteSubList(a, 0, 0);
 +        b = llDeleteSubList(b, 0, 0);
 +    } while(llGetListLength(a));
 +    return wasDualQuicksort(less, less_b) + pivot_a + pivot_b + wasDualQuicksort(more, more_b);
 +}
 + 
 +default
 +{
 +    state_entry()
 +    {
 +        list list_a = ["f", "a", "d", "e", "b", "c", "z"];
 +        list list_b = [ 10, 37,   1,   4,   2,   3 ,  1 ];
 +        
 +        llOwnerSay("Dual - Quicksort list contains in order: " + 
 +          llDumpList2String(wasDualQuicksort(list_a, list_b), " "));
 +    } 
 +}
 +</code>
 +
 +This holds only for a bijective map from the set defined by the elements of list_a to the set defined by the elements of list_b. The result is, as expected, a double-length list containing the elements of the first set merged with the elements of the second set:
 +
 +<code>
 +Object: Dual - Quicksort list contains in order: a 37 b 2 c 3 d 1 e 4 f 10 z 1
 +</code>
 +
 +This is a symmetry-based variation of [[secondlife:quicksort]].
 +
 +Concerning practical applications, this would be great to sort a $(NAME \times KEY)$ set pair; for example displaying avatar names alphabetically while preserving their corresponding keys in order.
 +
 +====== Map Preserving Sort using Quicksort for Integers ======
 +
 +The same as for strings, mentioned in the previous section on this page, but this time sorting integers. It will sort a map of two lists in decreasing order of the first list.
 +
 +Example:
 +<code lsl2>
 +wasDualQuicksort([10, 5, 1, 20, 2, 4, 8], ["a", "b", "c", "d", "e", "f", "g"])
 +</code>
 +will return the list:
 +<code>
 +20,d,10,a,8,g,5,b,4,f,2,e,1,c
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasDualQuicksort(list a, list b) {
 +    if(llGetListLength(a) <= 1) return a+b;
 +
 +    integer pivot_a = llList2Integer(a, 0);
 +    a = llDeleteSubList(a, 0, 0);
 +    string pivot_b = llList2String(b, 0);
 +    b = llDeleteSubList(b, 0, 0);
 +    
 +    list less = [];
 +    list less_b = [];
 +    list more = [];
 +    list more_b = [];
 +
 +    do {
 +        if(llList2Integer(a, 0) > pivot_a) {
 +            less += llList2List(a, 0, 0);
 +            less_b += llList2List(b, 0, 0);
 +            jump continue;
 +        }
 +        more += llList2List(a, 0, 0);
 +        more_b += llList2List(b, 0, 0);
 +@continue;
 +        a = llDeleteSubList(a, 0, 0);
 +        b = llDeleteSubList(b, 0, 0);
 +    } while(llGetListLength(a));
 +    return wasDualQuicksort(less, less_b) + [ pivot_a ] + [ pivot_b ] + wasDualQuicksort(more, more_b);
 +}
 +</code>
 +
 +====== RLV: Wearables & Attachment Lists as Strings ======
 +
 +**Local for less memory and less speed, global for more memory and more speed.**
 +<code lsl2>
 +//pragma inline
 +list CLOTHES = [ "gloves", "jacket", "pants", "shirt", "shoes", "skirt", "socks", "underpants", "undershirt" ];
 +//pragma inline
 +list ATTACHMENTS = [ "none", "chest", "skull", "left shoulder", "right shoulder", "left hand", "right hand",
 +                     "left foot", "right foot", "spine", "pelvis", "mouth", "chin", "left ear", "right ear", 
 +                     "left eyeball", "right eyeball", "nose", "r upper arm", "r forearm", "l upper arm", 
 +                     "l forearm", "right hip", "r upper leg", "r lower leg", "left hip", "l upper leg", 
 +                     "l lower leg", "stomach", "left pec", "right pec", "center 2", "top right", "top", 
 +                     "top left", "center", "bottom left", "bottom", "bottom right" ];
 +</code>
 +
 +====== RLV: Lock Attachments ======
 +
 +Place this script in an attachment to prevent it from being removed by RLV relays or outfit changes. Click the attachment and keep holding the left mouse button for a while (a rather long while) to unlock the attachment. Click the attachment again to lock it back into place. Nuked with [[secondlife:minify]] for fast deployment.
 +
 +<code lsl2>
 +integer z;default{state_entry(){llOwnerSay("Locked.");llOwnerSay("@detach=n");}touch_start(integer x)
 +{if(llDetectedKey(0)!=llGetOwner())return;z=0;}touch(integer y){++z;}on_rez(integer w){llResetScript();}
 +touch_end(integer y){if(z<100){z=0;return;}llOwnerSay("Unlocked.");llOwnerSay("@detach=y");z=0;
 +state unlocked;}}state unlocked{touch_start(integer x){llResetScript();}}
 +</code>
 +
 +====== RLV: Rotate Avatar To Face Target ======
 +
 +**Variables**
 +<code lsl2>
 +vector targetpos = < /* x */, /* y */, /* z */ >;
 +</code>
 +**Inline usage**
 +<code lsl2>
 +        vector pointTo = targetpos - llGetPos();
 +        float angleTo = llAtan2(pointTo.x, pointTo.y);
 +        llOwnerSay("@setrot:" + (string)angleTo + "=force");
 +</code>
 +
 +====== Key2Number: Passed to Rezzed Objects ======
 +
 +This should be used in both the rezzed object and the rezzer.
 +
 +**Global**
 +<code lsl2>
 +integer Key2Number(key objKey) {
 +  return -(integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
 +}
 +</code>
 +
 +This may be used anywhere in the rezzer script.
 +
 +**Rezzer: Inline usage**
 +<code lsl2>
 +llRezObject(/* object name to rez */, /* ... */ ,Key2Number(/* Key to be passed to the rezzed object. */))
 +</code>
 +
 +====== Key2Number: Homing Rezzed Object ======
 +
 +This an example application which, combined with the previous supersection, will move an object towards a target and will apply course-corrections every 1 seconds.
 +
 +Used in the rezzed object itself.
 +
 +**Rezzed object: Globals**
 +<code lsl2>
 +integer tgtID = 0;
 +key tgtKey = NULL_KEY;
 +integer nTgT = 0;
 +</code>
 +
 +Used in the rezzed object itself.
 +
 +**Rezzed object: Inline usage**
 +<code lsl2>
 +    at_target(integer tnum, vector targetpos, vector ourpos) {
 +        if(tgtKey == NULL_KEY) return;
 +        llSetTimerEvent(0);
 +
 +        /* Target has been reached. Do something. */        
 +
 +    }
 +    timer() {
 +        llSetTimerEvent(0);
 +        llTargetRemove(nTgT);
 +        vector pos = llList2Vector(llGetObjectDetails(tgtKey, [OBJECT_POS]), 0);
 +        nTgT = llTarget(pos, 1.0);
 +        llMoveToTarget(pos, 0.5);
 +        llSetTimerEvent(1);
 +    }
 +    on_rez(integer param) {
 +        tgtID = param;
 +        llSensorRepeat("", "", /* AGENT | ACTIVE | PASSIVE */, /* 96 */, /* arc */, llGetRegionTimeDilation()); 
 +    }
 +    sensor(integer num_detected) {
 +        --num_detected;
 +        do {
 +            tgtKey = llDetectedKey(num_detected);
 +            if(Key2Number(tgtKey) == tgtID) jump target_found;
 +        } while(--num_detected>-1);
 +        return;
 +@target_found;
 +        llSensorRemove();
 +        vector pos = llList2Vector(llGetObjectDetails(tgtKey, [OBJECT_POS]), 0);
 +        nTgT = llTarget(pos, 1.0);
 +
 +        /*
 +         * Object will start moving now. 
 +         * Add any pre-movement directives such as,
 +         * llSetStatus(STATUS_PHYSICS, TRUE); and others
 +         * instead of this comment here.
 +         */
 +
 +        llMoveToTarget(pos, 0.5);
 +        llSetTimerEvent(1);
 +    }
 +</code>
 +
 +====== Using Sensor as a Timer ======
 +
 +Can be used as a second ''timer'' event. This works by searching for the ''NULL_KEY'' agent (which does not exist) in a $0.1m$ range and will always trigger the ''no_sensor'' event handler.
 +
 +**Starting the sensor-timer**
 +<code lsl2>
 +        llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, 60);
 +</code>
 +**(No) Sensor Event Handler**
 +<code lsl2>
 +    no_sensor() {
 +
 +        /* this area will be hit 60 seconds
 +         * after the (no_)sensor handlers have
 +         * been installed with llSensorRepeat(). */
 +    }
 +</code>
 +
 +====== Sensor as alarm ======
 +
 +''llSensorRepeat'' can also be used as an alarm by rescheduling the ''llSensorRepeat'' sweep. That is, every time ''llSensorRepeat'' is called, it will trigger the ''no_sensor'' event handler.
 +
 +**Re-schedule alarm**
 +<code lsl2>
 +      llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, TIME);
 +</code>
 +where ''TIME'' is a number, representing the number of seconds until ''no_sensor'' is triggered.
 +
 +
 +**Clock - Triggered after TIME seconds**
 +<code lsl2>
 +    // triggered after alarm * t seconds.
 +    no_sensor() {
 +        /* this area will be hit TIME seconds. */
 +    }
 +</code>
 +
 +====== Data-Passing over States Changes without Global Variables ======
 +
 +One problem with switching states is that there is no way to pass a variable from one state to the other without using a global variable. However, the ''state'' page also mentions that the ''timer'' event is not reset. In that case, we may use something like the code below in order to slowly pass a variable onto the next state without using a global variable.
 +
 +**Example:**
 +<code lsl2>
 +default
 +{
 +    state_entry()
 +    {
 +        llSetTimerEvent(10); // <-- We pass the value 10.
 +        llResetTime(); // Set time mark.
 +        state catch;
 +    }
 +}
 +
 +state catch
 +{
 +    timer() {
 +        llSay(0, "Integer passed to me is: " + (string)((integer)llGetAndResetTime())); // Recover time mark.
 +        llSetTimerEvent(0);
 +    }
 +}
 +</code>
 +
 +**Output:**
 +
 +Delayed by 10 seconds:
 +
 +<code>
 +Object: Integer passed to me is: 10
 +</code>
 +
 +====== Move to Mobile Point between Two Points ======
 +
 +For [[secondlife:teleport]], we calculate a point that is at 10m between two points in space $A$ and $C$:
 +
 +<code>
 +
 +     d=10
 + +----------+
 +          B                    C
 + +----------+--------------------+
 +
 +</code>
 +
 +and we want to move an object from $A$ to $B$, $10m$ on the segment $A$C. In order to do that we calculate $B$'s position:
 +
 +We know that the distance ''dist'' of the segment $AC$ is:
 +<code lsl2>
 +float dist = llVectorDist(A, C); // calculated algebraically using generalized Pythagoras
 +</code>
 +Now, we know that we want to jump 10m towards point C, so in this case we want to jump 10 meters out of the entire distance:
 +<code lsl2>
 +jumpDistance = 10/dist;
 +</code>
 +So, the final position is given by:
 +<code lsl2>
 +final_position = posA + jumpDistance * (posC-posA);
 +</code>
 +
 +A function that computes $10m$ gates between two points is ''jumpGates'':
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list jumpGates(vector iPos, vector dPos, integer jumpDistance) {
 +    list gates = [];
 +    if(jumpDistance == 0) return gates;
 +    float dist = llVecDist(iPos, dPos);
 +    if(dist > jumpDistance) {
 +        iPos = iPos + jumpDistance * (dPos-iPos) / dist;
 +        gates += iPos;
 +        return gates + jumpGates(iPos, dPos, jumpDistance);
 +    }
 +    return gates + jumpGates(iPos, dPos, --jumpDistance);
 +}
 +
 +</code>
 +
 +====== Planar or Grid Distance ======
 +
 +{{ fuss_lsl_planar_distance.png?400 |Distance is marked with red }}
 +
 +The $x,y$ planar distance between the kitty and the book is given by:
 +<code lsl2>
 +float length_of_red_primitive=llVecDist(<kitty.x,kitty.y,0>,<book.x,book.y,0>);
 +</code>
 +
 +This works for all planes: $(x,y)$, $(x,z)$ and $(y,z)$ by eliminating one of the components - in this example, $z$.
 +
 +====== Object Rotate to Face Object ======
 +
 +{{ fuss_lsl_kitty_faces_book.png?400 |The rotation required to be applied to the kitty object in order to face the book. }}
 +
 +The rotation is given by $q\Delta$.
 +<code lsl2>
 +vector book_position = <83.889717, 92.310814, 500.5>;
 +vector kitty_position = <82.306671, 92.310814, 501.714783>;
 +
 +default {
 +    state_entry() {
 +        rotation qΔ = llRotBetween(<1,0,0>,llVecNorm(<book_position.x-kitty_position.x,
 +                                                      book_position.y-kitty_position.y,
 +                                                      book_position.z-kitty_position.z>));
 +        llSetRot(llGetRot() * qΔ);
 +    }
 +}
 +</code>
 +By eliminating a component $x$, $y$ or $z$ in ''llVecNorm'' the kitty may turn on only on certain axes.
 +
 +====== Propulse an Object towards Target (Artillery) ======
 +
 +This will hurl an object at a target using, in order:
 +
 +  - High angle.
 +  - Low angle.
 +  - Abort if object won't even lift off.
 +
 +More explanations on the [[secondlife:artillery|artillery page]].
 +
 +**Globals**
 +<code lsl2>
 +vector target = < /* x */, /* y */, /* z */ >;
 +float velocity = /* some velocity */;
 +</code>
 +**Inline usage**
 +<code lsl2>
 +        vector origin = llGetPos();
 +        dΔ=llVecDist(<target.x,target.y,0>,<origin.x,origin.y,0>);
 +        valSin = 9.81*dΔ/llPow(velocity, 2); /* Attempt high angle. */
 +        if(valSin < -1 || valSin > 1) valSin = 9.81/llPow(velocity, 2); /* Attempt low angle. */
 +        if(valSin < -1 || valSin > 1) return; /* Won't even lift the projectile off the ground. Abort. */
 +        llSetVelocity(llVecNorm(<1,0,0>*llRotBetween(<1,0,0>,llVecNorm(<target.x-origin.x,target.y-origin.y, 
 +        dΔ*llTan((90-RAD_TO_DEG*llAsin(valSin)/2) * DEG_TO_RAD) + 
 +                                                             llFabs(target.z-origin.z)>)))*velocity, FALSE);
 +</code>
 +
 +====== Pass Data to a Rezzed Object ======
 +
 +This will allow you to avoid using ''llRegionSay'', ''llWhisper'', ''llSay'' to pass data to a newly rezzed object. Meant for setting up private ''listen'' channels between the newly rezzed object and the rezzer by using the ''llDialog'' key hash method.
 +
 +**Hasher**
 +<code lsl2>
 +integer Key2Number(key objKey) {
 +  return -(integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
 +}
 +</code>
 +**Inline usage**
 +<code lsl2>
 +llRezObject("object name", /* params */,Key2Number(some key));
 +</code>
 +**Rezzed object event**
 +<code lsl2>
 +default
 +{
 +    on_rez(integer param) {
 +        /* param contains the passed data */
 +    }
 +}
 +</code>
 +
 +====== Persistent Storage ======
 +
 +{{indexmenu>secondlife:persistent_storage}}
 +
 +
 +====== Fast Texture Caching ======
 +
 +This is a texture cacher that quickly loads some textures on a 10-faced hidden primitive and is useful for beamers, talks, picture frames and other builds where the texture loading time can be annoying. It does that by permuting textures on each face, thereby forcing viewers in range to keep the texture alive.
 +
 +  * To preload a bunch of textures: 
 +<code lsl2>
 +llMessageLinked(/* link number the cacher script is in */, /* unused */, "kpre!", /* String of texture keys separated by commas. */);
 +</code>
 +where the string of textures separated by commas could be something like: "c5918ae1-d45c-feac-b64f-6f6c169fae3c,89368bf3-8136-122d-fbc7-28b1f533f157","d9d8fbd0-35dd-8617-c3f1-5165a54abd03", ...
 +
 +  * When the fast texture cacher obtained the data above, it sends a confirmation using: 
 +<code lsl2>
 +llMessageLinked(sender, 0, "kok", "");
 +</code>
 +where sender is the primitive that sent the caching request in the first place.
 +
 +**Full script:**
 +<file lsl2 ftc.lsl>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +
 +list slides = [];
 +integer sides = 0;
 + 
 +default {
 +    state_entry() {
 +        // Thanks for the music Salahzar Stenvaag, 10 faces is cool!
 +        llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_PRISM, 32, <0.199, 0.8, 0.0>, 0.30, <0.0, 0.0, 0.0>, 
 +        <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, PRIM_SIZE, <0.03, 2.89, 0.5>, PRIM_COLOR, ALL_SIDES, <1,1,1>, 0.0 ]);
 +        sides = llGetNumberOfSides();
 +    }
 +    
 +    timer() {
 +        string s = llList2String(slides, 0);
 +        llSetTexture(s, sides);
 +        slides = llDeleteSubList(slides, 0, 0);
 +        slides += s;
 +        if(--sides == 0) sides = llGetNumberOfSides();
 +    }
 +    
 +    link_message(integer sender, integer num, string message, key id) {
 +        if(message != "kpre!") return;
 +        llSetTimerEvent(0);
 +        slides = llCSV2List(id);
 +        llMessageLinked(sender, 0, "kok", "");
 +        llSetTimerEvent(1.01-llGetRegionTimeDilation());
 +    }
 +}
 +</file>
 +
 +====== Time Dilation Normed to 10 ======
 +
 +A short snippet that sets the primitive's text to the time dilation using the formula:
 +
 +<code>
 +dilation_norm_10 = (1.0 - dilation) * 10
 +</code>
 +
 +**Full script:**
 +<code lsl2>
 +default
 +{
 +    state_entry() {
 +        llSetTimerEvent(1);
 +    }
 +
 +    timer() {
 +        llSetText("Time Dilation: " + (string)((integer)((1.0-llGetRegionTimeDilation())*10.0)), <1,1,1>, 1.0);
 +    }
 +}
 +</code>
 +
 +====== Fast Integer Exponentiation ======
 +
 +Works for integers, ex: place a call with 
 +<code lsl2>
 +fsPow(2, 2) 
 +</code>
 +for the function to return the value of 2^2.
 +
 +<code lsl2>
 +integer fsPow(integer base, integer exponent) {
 +    integer result = 1;
 +    do {
 +        if(exponent & 1) result *= base;
 +        exponent = exponent >> 1;
 +        base *= base;
 +    } while(exponent);
 +    return result;
 +}
 +</code>
 +
 +====== Xorshift ======
 +
 +Fast random number generator, invented by [[http://en.wikipedia.org/wiki/George_Marsaglia|George Marsaglia]], implemented in LSL by us and passes all the [[http://en.wikipedia.org/wiki/Diehard_tests|diehard tests]].
 +
 +<code lsl2>
 +integer x = 123456789;
 +integer y = 362436069;
 +integer z = 521288629;
 +integer w = 88675123;
 +  
 +integer xor128() {
 +  integer t;
 +  t = x ^ (x << 11);
 +  x = y; y = z; z = w;
 +  return w = w ^ (w >> 19) ^ (t ^ (t >> 8));    
 +}
 +</code>
 +
 +====== TRNG ======
 +
 +True random-number generator, based on FPS fluctuations of region frame-rates as discussed on the page at [[drafts:randomness_entropy_and_statistics|Randomness, Entropy and Statistics]].
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// Requires: a limit.
 +// Provides: true random number between [0, max) or (-max, 0].
 +//////////////////////////////////////////////////////////
 +integer wasFPSrand(integer max) {
 +    integer r = (integer)(llGetRegionFPS() * 10000000.0) % max;
 +    if(max > 0) return r; else return -r;
 +}
 +</code>
 +
 +====== Element Selection Based on Weighted Probabilities ====== 
 +
 +A cumulative algorithm that may be used to select an element from a list, where each element is weighted with different probabilities.
 +
 +For example, selecting an element from the list below based on the corresponding probabilities:
 +<code>
 +A -> 60%
 +B -> 10%
 +C -> 10%
 +D -> 20%
 +</code>
 +
 +Can be done using the cumulative algorithm below. The dualQuicksort can be obtained from this page, in a previous section and is there because in certain circumstances one may calculate the probabilities for A, B, C and D dynamically without any previous knowledge.
 +
 +<code lsl2>
 +    list sorted = dualQuicksort([ 60, 10, 10, 20 ], [ "A", "B", "C", "D" ]);
 +    list identifier_list = llList2ListStrided(llDeleteSubList(sorted, 0, 0), 0, llGetListLength(sorted)-1, 2);
 +    list identifier_probabilities = llList2ListStrided(sorted, 0, llGetListLength(sorted)-1, 2);
 +    float rnd = llFrand(1);
 +    float cum = 0;
 +    string select = "";
 +    integer itra = llGetListLength(alleles);
 +    do {
 +      cum += llList2Float(identifier_probabilities, itra);
 +      if(cum >= rnd) {
 +        select = llList2String(identifier_list, itra);
 +        jump draw;
 +      }
 +  } while(--itra>-1);
 +@draw;
 +  // the variable select now contains the identifier A, B, C or D
 +  // based on the probabilities mentioned above.
 +</code>
 +
 +====== Compute Capping System Delays ======
 +
 +A calculator to compute the random amount of time needed (in seconds) to wait between executing some capped action as discussed in our article [[drafts:randomness_entropy_and_statistics|Randomness, Entropy and Statistics]].
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// Requires: a cap limit and an initial pad, usually 0.
 +// Provides: a random interval which must be multiplied by 
 +// number of queued actions.
 +//////////////////////////////////////////////////////////
 +float minCapDodgeTimeF(float cap, float pad) {
 +    if(cap < 0.1) return pad;
 +    pad += cap / 2.0;
 +    cap /= 10.0;
 +    return minCapDodgeTimeF(cap, pad);
 +}
 +</code>
 +
 +====== wasSpaceWrap ======
 +
 +This function wraps a string ''txt'' after ''column'' characters, when the first space is found, by replacing the space character with the ''delimiter'' string at that point. It is somewhat similar to what ''SplitLine'' by Huney Jewell is intending to do, with the difference that ''wasSpaceWrap'' is whitespace aware and useful to separate entire words rather than breaking them in the middle.
 +
 +Example, suppose column is ''10'', delimiter is ''"\n"'' and the text is the ''Marry Had a Little Lamb'' nursery song:
 +<code>
 +Marry had a
 +little lamb,
 +Its fleece
 +was white
 +as snow; And 
 +everywhere
 +that Mary
 +went, The
 +lamb was sure
 +to go.
 +</code>
 +
 +**Global**
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// Requires: a string txt, a delimiter, a column number
 +// Provides: a string split at the first space after column
 +//////////////////////////////////////////////////////////
 +string wasSpaceWrap(string txt, string delimiter, integer column) {
 +    string ret = llGetSubString(txt, 0, 0);
 +    integer len = llStringLength(txt);
 +    integer itra=1;
 +    integer itrb=1;
 +    do {
 +        if(itrb % column == 0) {
 +            while(llGetSubString(txt, itra, itra) != " ") {
 +                ret += llGetSubString(txt, itra, itra);
 +                if(++itra>len) return ret;
 +            }
 +            ret += delimiter;
 +            itrb = 1;
 +            jump next;
 +        }
 +        ret += llGetSubString(txt, itra, itra);
 +        ++itrb;
 +@next;
 +    } while(++itra<len);
 +    return ret;
 +}
 +</code>
 +
 +====== Rename Primitives in a Linked Set ======
 +
 +Uses the new ''llTextBox'', nuked with [[secondlife:minify]] for fast deploy.
 +
 +<code lsl2>
 +default{touch_start(integer z){integer x=-(integer)llFrand(10);llListen(x,"",llGetOwner(),"");llTextBox(
 +llGetOwner(),"\nPlease type the w to set the primitives to: ",x);}listen(integer y, string w,key v,
 +string u){integer t=llGetNumberOfPrims();do {llSetLinkPrimitiveParamsFast(t,[PRIM_NAME,u]);}
 +while(--t>=1);llOwnerSay("OK");llRemoveInventory(llGetScriptName());}}
 +</code>
 +
 +====== String Reverse ======
 +
 +Recursive string reversal; sweet solution from [[http://codemonkeyism.com/java-interview-questions-write-a-string-reverser-and-use-recursion/|code monkeys]].
 +
 +**Global**
 +
 +<code lsl2>
 +string wasStringReverse(string str) {
 +    if(llStringLength(str)<=1) return str;
 +    return wasStringReverse(llGetSubString(str,1,llStringLength(str))) + llGetSubString(str,0,0);
 +}
 +</code>
 +
 +**Example**
 +<code lsl2>
 +llOwnerSay(wasStringReverse("שלום, עולם!"));
 +</code>
 +
 +**Output**
 +<code>
 +Object: !םלוע ,םולש
 +</code>
 +
 +====== List Reverse ======
 +
 +Same as above but using lists instead of strings.
 +
 +**Global**
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasListReverse(list lst) {
 +    if (llGetListLength(lst) <= 1) return lst;
 +    return wasListReverse(
 +        llList2List(
 +            lst, 
 +            1, 
 +            llGetListLength(lst)
 +        )
 +    ) + llList2List(lst, 0, 0);
 +}
 +</code>
 +
 +**Example**
 +<code lsl2>
 +list CLOTHES = [ "gloves","jacket","pants","shirt","shoes","skirt","socks","underpants","undershirt","skin","eyes","hair","shape" ];
 +llOwnerSay(llDumpList2String(wasListReverse(CLOTHES), " "));
 +</code>
 +
 +**Output**
 +<code>
 +shape hair eyes skin undershirt underpants socks skirt shoes shirt pants jacket gloves
 +</code>
 +
 +====== XML Stream Parser ======
 +
 +This is a StAX  stream parser which is able to read ''simplexml''.
 +===== Get Node Value =====
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasStAX_GetNodeValue(string xmlStream, string node) {
 +    list stream = llParseString2List(xmlStream, [" "], ["<", ">", "/"]);
 +    integer size = llGetListLength(stream);
 +    string value = "";
 +    integer ptr = 0;
 +    do {
 +        string current = llList2String(stream, ptr);
 +        string lookback = llList2String(stream, ptr-1);
 +
 +        if(current != "/" && lookback == "<") {
 +            StAX += current;
 +            jump next_tag;
 +        }
 +        if(lookback == "/") {
 +            StAX = llDeleteSubList(StAX, llGetListLength(StAX)-1, llGetListLength(StAX)-1);
 +            jump next_tag;
 +        }
 +        if(current != ">" && current != "/" && current != "<"
 +            if(llList2String(StAX,llGetListLength(StAX)-1) == node)
 +                value += current + " ";  
 +@next_tag;
 +    } while(++ptr<size);
 +    
 +    if(llGetListLength(StAX) != 0) {
 +        llSay(DEBUG_CHANNEL, "The following tags may be unmatched: " + llDumpList2String(StAX, ",") + ". Please check your file.");
 +    }
 +    return value;
 +}
 +</code>
 +
 +===== Set Node Value =====
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasStAX_SetNodeValue(string xmlStream, string node, string value) {
 +    list stream = llParseString2List(xmlStream, [""], ["<", ">", "/"]);
 +    integer size = llGetListLength(stream);
 +    list StAX = [];
 +    integer ptr = 0;
 +    integer set = 0;
 +    do {
 +        string current = llList2String(stream, ptr);
 +        string lookback = llList2String(stream, ptr-1);
 + 
 +        if(current != "/" && lookback == "<") {
 +            StAX += current;
 +            jump next_tag;
 +        }
 +        if(lookback == "/") {
 +            StAX = llDeleteSubList(StAX, llGetListLength(StAX)-1, llGetListLength(StAX)-1);
 +            jump next_tag;
 +        }
 +        if(current != ">" && current != "/" && current != "<"
 +            if(llList2String(StAX,llGetListLength(StAX)-1) == node) {
 +                if(!set) {
 +                    stream = llListReplaceList(stream, (list)value, ptr, ptr);
 +                    set = 1;
 +                    jump next_tag;
 +                }
 +                stream = llListReplaceList(stream, (list)"", ptr, ptr);
 +            }
 +@next_tag;
 +    } while(++ptr<size);
 + 
 +    if(llGetListLength(StAX) != 0) {
 +        llSay(DEBUG_CHANNEL, "The following tags may be unmatched: " + llDumpList2String(StAX, ",") + ". Please check your file.");
 +    }
 +    return llDumpList2String(stream, "");
 +}
 +</code>
 +
 +====== wasBioLuminescence ======
 +
 +Can be called every hour to make lagoons glow and simulate oceans or seas.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +float wasBioLuminescence(float hours) {
 +    float sin = llSin(0.1*hours);
 +    if(sin>0.5) return sin-0.5;
 +    return sin;
 +}
 +</code>
 +
 +
 +====== Two in One Integers ======
 +
 +Two integers ''var_a'' and ''var_b'' can be masked in one single variable (for example, for passing the parameter to the ''on_rez'' event handler):
 +
 +Pack:
 +<code lsl2>
 +var_c = var_a << 16 | var_b
 +</code>
 +
 +Unpack:
 +<code lsl2>
 +var_a = var_c >> 16;
 +var_b = var_c & 0xFFFF;
 +</code>
 +
 +====== Permute Sort ======
 +
 +Permute sort, uses a reference string to sort an input string. 
 +
 +Example reference:
 +<code>
 +rraaats
 +</code>
 +example input:
 +<code>
 +taaas
 +</code>
 +example output:
 +<code>
 +aaats
 +</code>
 +
 +<code lsl2>
 +/////////////////////////////////////////////
 +// [K] Permute Sort is an algorithm to     //
 +// sort two strings depending on the       //
 +// relative positions of their elements.   //
 +/////////////////////////////////////////////
 +string permuteSort(string input, string refer) {
 +
 +    integer itra = llStringLength(refer)-1;
 +    do {
 +        string s = llGetSubString(refer, itra, itra);
 +        if(llSubStringIndex(input, s) == -1) {
 +            refer = llDeleteSubString(refer, itra, itra);
 +        }
 +    } while(--itra>=0);
 +    return refer;
 +    
 +}
 +</code>
 +
 +====== Float Limits ======
 +
 +^ Minimum Value   ^ Maximum Value   ^ Cast Infinite (Dangerous for LSO) ^
 +| ''1.175494351E-38'' | ''3.402823466E+38'' | ''(float)"inf"''                      |
 +
 +====== Subtract Value from Value List ======
 +Example input, ''input'' parameter:
 +<code>
 +2 3 8 2 4 6
 +</code>
 +and ''value'' parameter equal to ''1''.
 +
 +Result:
 +<code>
 +1 2 7 1 3 5
 +</code>
 +
 +Returns ''-1'' on empty list. ''integer'' type can be replaced with ''float'' for subtracting ''float'' values.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasListSubtract(list input, integer value) {
 +    integer v = llList2Integer(input, 0);
 +    input = llDeleteSubList(input, 0, 0);
 +    if(input == []) return [v-value] + input;
 +    return [v-value] + wasListSubtract(input, value);
 +}
 +</code>
 +
 +====== Add-up all List Elements ======
 +
 +The following recursive function can be used instead of ''llListStatistics''.
 +
 +Example input:
 +<code>
 +2 3 8 2 4 6
 +</code>
 +Example output:
 +<code>
 +25
 +</code>
 +
 +Returns ''0'' on empty list.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasAddListElements(list input) {
 +    if(input == []) return 0;
 +    return llList2Integer(input, 0) + 
 +        wasAddListElements(llDeleteSubList(input, 0, 0));
 +}
 +</code>
 +
 +====== Remove Multiple Elements From List ======
 +
 +Input:
 +<code lsl2>
 +wasSubtractSubList(["a", "b", "c", "d"], ["c", "d"])
 +</code>
 +
 +Output:
 +<code>
 +a b
 +</code>
 +
 +Input:
 +<code lsl2>
 +wasSubtractSubList(["a", "b", "c", "d"], ["b", "d"])
 +</code>
 +
 +Output:
 +<code>
 +a c
 +</code>
 +
 +Input:
 +<code lsl2>
 +wasSubtractSubList(["a", "b", "c", "d"], ["b", "a"])
 +</code>
 +
 +Output:
 +<code>
 +c d
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasSubtractSubList(list input, list delete) {
 +    do {
 +        string tok = llList2String(delete, 0);
 +        list clean = input;
 +        do {
 +            if(llList2String(clean, 0) != tok) jump skip; 
 +            integer idx = llListFindList(input, (list)tok);
 +            input = llDeleteSubList(input, idx, idx);
 +@skip;
 +            clean = llDeleteSubList(clean, 0, 0);
 +        } while(llGetListLength(clean));
 +        delete = llDeleteSubList(delete, 0, 0);
 +    } while(llGetListLength(delete));
 +    return input;
 +}
 +</code>
 +
 +====== Generate UUID v4 ======
 +
 +Input:
 +<code lsl2>
 +        key gen = wasUUIDgen(234234);
 +        if(gen) {
 +            llOwnerSay("Valid key: " + (string)gen);
 +        }
 +</code>
 +
 +Output:
 +<code>
 +Valid key: e5259984-f745-4398-a5f7-2317cc724d9a
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +key wasUUIDgen(integer seed) {
 +    // UUIDv4
 +    // xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
 +    // x - hex
 +    // y - 8, 9, A or B
 +    string result = "";
 +    integer itra = 0;
 +    do {
 +        string n = "";
 +        if(itra == 8 || itra == 13 || itra == 18 || itra == 23) {
 +            n = "-";
 +            jump append;
 +        }
 +        if(itra == 14) {
 +            n = "4";
 +            jump append;
 +        }
 +        if(itra == 19) {
 +            n = llList2String(["8", "9", "a", "b"], (integer)llFrand(4+llAbs(seed)) % 4);
 +            jump append;
 +        }
 +        n = llList2String(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"], (integer)llFrand(15+llAbs(seed)) % 15);
 +@append;
 +        result += n;
 +    } while(++itra<36);
 +    return (key)result;
 +}
 +</code>
 +
 +====== Levenshtein Distance ======
 +
 +The Levenshtein distance is equal to the number of single-character edits required to change one word into the other.
 +
 +Input:
 +<code lsl2>
 +wasLevenshteinDistance("kitten", "sitting")
 +</code>
 +
 +Return:
 +<code>
 +3
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasLevenshteinDistance(string a, string b) {
 +    if(a == b) return 0;
 +    
 +    integer sa = llStringLength(a);
 +    integer sb = llStringLength(b);
 +    
 +    if(sa == 0)
 +        return sb;
 +    if(sb == 0)
 +        return sa;
 +        
 +    list v0 = [];
 +    list v1 = [];
 +    
 +    integer i = 0;
 +    do {
 +        v0 += i;
 +    } while(++i < sb + 1);
 +    
 +    i = 0;
 +    do {
 +        v1 += 0;
 +    } while(++i < sb + 1);
 +    
 +    i = 0;
 +    do {
 +        
 +        v1 = llListReplaceList(v1, (list)(i + 1), 0, 0);
 +
 +        integer j = 0;
 +        do {
 +            integer cost = 0;
 +            if(llGetSubString(a, i, i) != llGetSubString(b, j, j))
 +                cost = 1;
 +            integer x = llList2Integer(v1, j) + 1;
 +            integer y = llList2Integer(v0, j + 1) + 1;
 +            integer z = llList2Integer(v0, j) + cost;
 +            v1 = llListReplaceList(v1,
 +                (list) (
 +                    llAbs((llAbs(x >= y) * y) > z) * z + 
 +                    llAbs((llAbs(y > x) * x) >= z) * z + 
 +                    llAbs((llAbs(z >= x) * x) > y) * y +
 +                    llAbs((llAbs(x > z) * z) >= y) * y +
 +                    llAbs((llAbs(y >= z) * z) > x) * x +
 +                    llAbs((llAbs(z > y) * y) >= x) * x | 
 +                    llAbs((llAbs(x == y) * x) == z) * x
 +                ),
 +                j + 1,
 +                j + 1
 +            );
 +        } while(++j < sb);
 +        
 +        j = 0;
 +        do {
 +            v0 = llListReplaceList(
 +                v0, 
 +                (list)llList2String(
 +                    v1,
 +                    j
 +                ),
 +                j, 
 +                j
 +            );
 +        } while(++j < sb);
 +        
 +    } while(++i < sa);
 +    
 +    return llList2Integer(v1, sb);
 +}
 +</code>
 +====== Minimal and Maximal Value in List ======
 +
 +This is a fast recursive alternative to using ''llListStatistics''. It returns either the minimal or the maximal value in a list of numbers (integers or floats).
 +
 +Input:
 +<code lsl2>
 +wasMinimum([10, 1, 9, 20, 8])
 +</code>
 +
 +Return:
 +<code>
 +1
 +</code>
 +
 +===== Minimal Value =====
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasMinimum(list integers) {
 +    if(llGetListLength(integers) == 1) {
 +        return llList2Integer(integers, 0);
 +    }
 +    integer i = llList2Integer(integers, 0);
 +    integer j = llList2Integer(integers, 1);
 +    if(j < i) {
 +        integers = llDeleteSubList(integers, 0, 0);
 +        integers = llListReplaceList(integers, (list)j, 0, 0);
 +    }
 +    integers = llDeleteSubList(integers, 1, 1);
 +    return wasMinimum(integers);
 +}
 +</code>
 +===== Maximal Value =====
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasMaximum(list integers) {
 +    if(llGetListLength(integers) == 1) {
 +        return llList2Integer(integers, 0);
 +    }
 +    integer i = llList2Integer(integers, 0);
 +    integer j = llList2Integer(integers, 1);
 +    if(j > i) {
 +        integers = llDeleteSubList(integers, 0, 0);
 +        integers = llListReplaceList(integers, (list)j, 0, 0);
 +    }
 +    integers = llDeleteSubList(integers, 1, 1);
 +    return wasMaximum(integers);
 +}
 +</code>
 +
 +===== Reasoning =====
 +
 +The reasoning is based on using lists as queues:
 +
 +  - [Base Case] **If** the queue contains just one element, **return** that element.
 +  - **If** the second element in the queue is smaller than the first element in the queue:
 +    -  Delete the first element in the queue and replace it with the second element in the queue.
 +  - Delete the second element in the queue.
 +  - **return** queue of (1).
 +
 +The only difference between ''wasMinimum'' and ''wasMaximum'' is that the sign is inverted in the comparison:
 +
 +<code lsl2>
 +    if(j < i) {
 +</code>
 +
 +for finding the minimal value, and
 +
 +<code lsl2>
 +    if(j > i) {
 +</code>
 +
 +for finding the maximal value.
 +
 +The algorithm is not limited to integers and will work for floats as well.
 +
 +====== Find Unique Elements in List ======
 +
 +''wasListUnique'' returns a sorted list of unique elements by deleting re-occuring elements in the input list. The function takes two parameters:
 +
 +  * ''in'', the list to clean of duplicates.
 +  * ''sorted'', a boolean specifying whether the list is already sorted.
 +
 +The choice of the ''sorted'' parameter results in two different time complexities for the ''wasListUnique'' function:
 +
 +  * When the list is not already sorted (the ''sorted'' parameter being ''FALSE''), the time complexity is ''O(sort + n)''. Since ''llListSort'' sorts the elements in the list using bubble sort, the resulting complexity is ''O(n^2 +n)''. Alternatively, if a different sorting method is used with logarithmic complexity, such as Quicksort, then the time complexity of the function would be ''O(logn +n)''.
 +  * When the list is already sorted (the ''sorted'' parameter being ''TRUE''), the time complexity is ''O(n)'' since the algorithm traverses the list only once.
 +
 +Call:
 +<code lsl2>
 +wasListUnique([1, 1, 2, 1, 3, 1, 5, 2, 2, 2, 7, 8], FALSE)
 +</code>
 +
 +Return:
 +<code>
 +[1, 2, 3, 5, 7, 8]
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasListUnique(list in, integer sorted) {
 +    if(llGetListLength(in) == 0) return [];
 +    if(sorted == TRUE) jump sorted;
 +    in = llListSort(in, 1, TRUE);
 +@sorted;
 +    string a = llList2String(in, 0);
 +    string b = llList2String(in, 1);
 +    list result = [];
 +    if(a == b) jump dupe;
 +    result += a;
 +@dupe;
 +    return result + wasListUnique(llDeleteSubList(in, 0, 0), TRUE);
 +}
 +</code>
 +
 +Depending on the data-types contained in the input list ''in'', the ''wasListUnique'' function works differently:
 +
 +  * In case the input list ''in'' contains a list of unsorted elements of the same data-type, ''wasListUnique'' returns a sorted list of unique elements from the input list.
 +  * In case the input list ''in'' contains an already sorted list of different data-types, ''wasListUnique'' returns a sorted list of unique elements from the input list.
 +  * In case the input list ''in'' contains a list of unsorted elements of different data-types, ''wasListUnique'' inherits the arbitrary behavior of ''llListSort'' and returns arbitrary results.
 +
 +====== Delete Sub List Elements Matching a String ======
 +
 +The function takes two parameters:
 +
 +  * an input list ''in''
 +  * a string ''match''
 +
 +and returns the elements in list ''in'' that do not have ''match'' as a substring.
 +
 +Call:
 +<code lsl2>
 +wasDeleteSubListMatch(["aabbcc", "dddd", "eeffgg", "rrraatt"], "e")
 +</code>
 +
 +Return:
 +<code>
 +[ "aabbcc", "dddd", "rrraatt"]
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasDeleteSubListMatch(list in, string match) {
 +    if(llGetListLength(in) == 0) return [];
 +    string first = llList2String(in, 0);
 +    in = llDeleteSubList(in, 0, 0);
 +    if(llSubStringIndex(first, match) == -1) jump next;
 +    return wasDeleteSubListMatch(in, match);
 +@next;
 +    return first + wasDeleteSubListMatch(in, match);
 +}
 +</code>
 +
 +====== Number to Roman Numeral ======
 +
 +Input:
 +<code>
 +23
 +</code>
 +
 +Output:
 +<code>
 +XXIII
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasNumberToRoman(integer num) {
 +    list n = [ 1000, 900, 500, 400, 100,90, 50, 40, 10, 9, 5, 4, 1 ];
 +    list r = [ "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" ];
 +    integer i = 0;
 +    string result = "";
 +    do {
 +        while(num >= llList2Integer(n, i)) {
 +            num -= llList2Integer(n, i);
 +            result += llList2String(r, i);
 +        }
 +    } while(++i < llGetListLength(n));
 +    return result;
 +}
 +</code>
 +
 +====== Pretty Numbers ======
 +
 +Call:
 +<code lsl2>
 +wasPrettyNumbers(15, [ "⓪", "⓵", "⓶", "⓷", "⓸", "⓹", "⓺", "⓻", "⓼", "⓽" ]);
 +</code>
 +
 +Return:
 +<code lsl2>
 +⓵⓹
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasPrettyNumbers(integer num, list modNine) {
 +    list d = llParseString2List((string)num, [], ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
 +    if(llGetListLength(d) == 0) return llList2String(modNine, 0);
 +    string r = "";
 +    do {
 +        r += llList2String(modNine, llList2Integer(d, 0));
 +        d = llDeleteSubList(d, 0, 0);
 +    } while(llGetListLength(d));
 +    return r;
 +}
 +</code>
 +
 +====== Permute a Set ======
 +
 +Returns a list containing all the permutations of a set.
 +
 +A call such as:
 +<code lsl2>
 +wasPermuteSet(["a", "b"], 0, 2)
 +</code>
 +where ''0'' represents the start index and ''2'' represents the length of the set, will return the list containing the elements:
 +<code lsl2>
 +a b b a
 +</code>
 +
 +''wasPermuteSet'' is a recursive function that uses ''wasListSwap'' as an auxiliary function:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// swaps two elements in a list
 +list wasListSwap(list set, integer a, integer b) {
 +    list t = llList2List(set, a, a);
 +    set = llListReplaceList(set, llList2List(set, b, b), a, a);
 +    set = llListReplaceList(set, t, b, b);
 +    return set;
 +}
 +
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// retuns a set that contains all the permutations from the given set
 +list wasPermuteSet(list set, integer start, integer end) {
 +    list result = [];
 +    if(start == end) return set;
 +    integer i = start;
 +    do {
 +        set = wasListSwap(set, start, i);
 +        result += wasPermuteSet(set, start+1, end);
 +        set = wasListSwap(set, start, i);
 +    } while(++i<end);
 +    return result;
 +}
 +</code>
 +
 +====== Calculate Factorial ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// Calculates n!
 +integer wasFactorial(integer n) {
 +    if(n == 1) return 1;
 +    return wasFactorial(--n) * n;
 +}
 +</code>
 +
 +====== Insertion Sort ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasInsertSort(list toSort) {
 +    list sorted = [];
 +@next_element;
 +    integer n = llList2Integer(toSort, 0);
 +    toSort = llDeleteSubList(toSort, 0, 0);
 +    integer s = llGetListLength(sorted)-1;
 +    if(s == -1) {
 +        sorted += n;
 +        jump next_element;
 +    }
 +    do {
 +        integer sn = llList2Integer(sorted, s);
 +        if(n < sn) jump continue;
 +        sorted = llListInsertList(sorted, (list)n, s+1);
 +        if(llGetListLength(toSort)) jump next_element;
 +        return sorted;
 +@continue;
 +    } while(--s>-1);
 +    sorted = llListInsertList(sorted, (list)n, 0);
 +    if(llGetListLength(toSort)) jump next_element;
 +    return sorted;
 +}
 +</code>
 +
 +====== Remove Bling and Particle Effects ======
 +
 +Copy and paste into any script inside a primitive to remove the bling and any other particle effects.
 +<code lsl2>
 +default{state_entry(){llParticleSystem([]);}}
 +</code>
 +
 +====== Remove Texture Animation ======
 +
 +Copy and paste into any script inside a primitive to stop all texture animations.
 +<code lsl2>
 +default{state_entry(){llSetTextureAnim(0,-1,0,0,0.0,0.0,1.0);}}
 +</code>
 +
 +====== Percent to Gradient ======
 +
 +A color in LSL is represented by a vector where the $x$, $y$ and $z$ components represent the red, green and blue colors respectively. Given a gradient path between two colors, the following function will return a vector that represents a percentage of that path. The function can be used to create nice gradients, such as the ones used for the health meters in [[secondlife:zombies|the zombies game]]. For a demonstration see the [[secondlife:bistable_color_primitive|bistable color primitive page]].
 +
 +For example, a call such as:
 +<code lsl2>
 +wasPercentToGradient(50, "rg");
 +</code>
 +will return the color vector ''<1,1,0>'' (yellow) which represents $50\%$ of the path between red (''r'') and green (''g'').
 +
 +The ''rgb'' parameter for different gradients has to be two letters long:
 +^ ''rgb'' parameter ^ gradient                ^
 +| ''rg''            | red (''0%'') to green (''100%'' |
 +| ''rb''            | red (''0%'') to blue (''100%''  |
 +| ''gb''            | green (''0%'') to blue (''100%'') |
 +| ''gr''            | green (''0%'') to red (''100%'' |
 +| ''br''            | blue (''0%'') to red (''100%''  |
 +| ''bg''            | blue (''0%'') to green (''100%'') |
 +
 +The function does not convert from HSV, instead it eliminates the saturation problem by doubling the output vector and benefits from the fact that color vectors in LSL have their components scaled to ''1''. In other words, this function will return ''<2,0,0>'' for the color red, which is pedantically incorrect, but when passed as a color vector, the runtime will automatically scale it to ''<1,0,0>''.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +vector wasPercentToGradient(float percent, string rgb) {
 +    if(llStringLength(rgb) != 2) {
 +        llSay(DEBUG_CHANNEL, "Assert failed, rgb parameter must consist of a pair of either r, g, or b.");
 +        return ZERO_VECTOR;
 +    }
 +    string a = llGetSubString(rgb, 0, 0);
 +    string b = llGetSubString(rgb, 1, 1);
 +    list col = [ "r", "g", "b" ];
 +    integer ax = llListFindList(col, (list)a);
 +    integer bx = llListFindList(col, (list)b);
 +    if(ax == -1 || bx == -1) {
 +        llSay(DEBUG_CHANNEL, "Asset failed, rgb parameters must contain either r, g, or b letters.");
 +        return ZERO_VECTOR;
 +    }
 +    col = llListReplaceList(col, (list)((100-percent)/100), ax, ax);
 +    col = llListReplaceList(col, (list)(percent/100), bx, bx);
 +    return 2*<llList2Float(col, 0), llList2Float(col, 1), llList2Float(col, 2)>;
 +}
 +</code>
 +
 +====== Percent to Brightness Gradient ======
 +
 +When ''RGB'' should not be the slider but rather the brightness of the color (less common), then the following function would set the brightness ''percent'' between ''from'' and ''to'' color vectors.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +vector wasPercentToGradientBrighness(float percent, vector from, vector to) {
 +    return <from.x + (to.x-from.x)*percent/100,
 +        from.y + (to.y-from.y)*percent/100, 
 +        from.z + (to.z-from.z)*percent/100>;
 +}
 +</code>
 +
 +====== Shuffle Lists ======
 +
 +Some remarks:
 +
 +  * if we were to calculate the [[fuss:lsl#levenshtein_distance|Levenshtein distance]] of a [[fuss:lsl#knuth_shuffle|Knuth shuffle]], the Levenshtein distance will always be equal to the number of characters of the returned. This is because the Knuth shuffle swaps every element on every iteration.
 +  * on the other hand, the [[fuss:lsl#shuffle_list_randomly|wasShuffleList]] function, does allow the selected element to be the current element so that its position is never exchanged.
 +
 +===== Knuth Shuffle ========
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasKnuthShuffle(list data) {
 +    integer i=llGetListLength(data)-1;
 +    do {
 +        string a = llList2String(data, i);
 +        integer rnd = (integer)llFrand(i);
 +        string b = llList2String(data, rnd);
 +        data = llListReplaceList(data, (list)b, i, i);
 +        data = llListReplaceList(data, (list)a, rnd, rnd);
 +    } while(--i>-1);
 +    return data;
 +}
 +</code>
 +
 +
 +===== Shuffle List Randomly =====
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasShuffleList(list data) {
 +    if(data == []) return [];
 +    integer rnd = (integer)llFrand(llGetListLength(data));
 +    return [ llList2String(data, rnd) ] + 
 +        wasShuffleList(llDeleteSubList(data, rnd, rnd));
 +}
 +</code>
 +
 +====== Binary Search ======
 +
 +Binary search expects the ''data'' list to be sorted and returns the element if found or ''-1'' otherwise.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +float wasBinarySearch(list data, float item) {
 +    // assert(data is sorted)
 +    if(data == []) return -1;
 +    integer l = llGetListLength(data);
 +    integer m = l/2;
 +    float mItem = llList2Float(data, m);
 +    if(mItem == item) return mItem;
 +    if(mItem < item) {
 +        data = llDeleteSubList(data, 0, m);
 +        jump continue;
 +    }
 +    data = llDeleteSubList(data, m, l-1);
 +@continue;
 +    return wasBinarySearch2(data, item);
 +}
 +</code>
 +
 +For a complete binary-tree and binary search tree (BST) implementation in LSL see the [[secondlife:binary_trees|binary trees section]].
 +===== Notes =====
 +
 +[[fuss:lsl#binary_search|wasBinarySearch]] is twice faster than the built-in API function ''llListFindList'' when the ''data'' list is sorted. For example, the following is a trial run on a dataset of ''1000'' sequential items generated with:
 +
 +<code bash>
 +jot - 1 1000
 +</code>
 +
 +The goal was to find the element ''53'', which shows how binary search outperforms ''llListFindList'' by far:
 +
 +<code>
 +Object: Binary search, found: 53.000000
 +Object: Binary search took: 0.004547 seconds
 +Object: llListFindList found 53 at index: 52
 +Object: llListFindList took: 0.021926 seconds
 +</code>
 +
 +On subsequent runs, the same delta was observed between [[fuss:lsl#binary_search|wasBinarySearch]] and ''llListFindList''.
 +
 +===== Strings =====
 +
 +For strings, the procedure is similar, just that to be in-tone with UTF-8 and allow the binary-search to work with non-latin characters, we need to use ''Ord'' by Pedro Oval in order to be able to compare strings:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//         Ord() function, written by Pedro Oval, 2010-05-28             //
 +///////////////////////////////////////////////////////////////////////////
 +integer Ord(string chr) {
 +    if (chr == "") return 0;
 +    string hex = llEscapeURL(chr);
 +    if (llGetSubString(hex, 0, 0) != "%"
 +        return llBase64ToInteger("AAAA"
 +            llStringToBase64(llGetSubString(chr, 0, 0)));
 +    integer b = (integer)("0x" + llGetSubString(hex, 1, 2));
 +    if (b < 194 || b > 244) return b;
 +    if (b < 224) return ((b & 0x1F) << 6) | 
 +        (integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F;
 +    if (b < 240) return (b & 0x0F) << 12 + 
 +        ((integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F) << 6 + 
 +        (integer)("0x" + llGetSubString(hex, 7, 8)) & 0x3F;
 +    return (b & 0x07) << 18 + 
 +        ((integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F) << 12 + 
 +        ((integer)("0x" + llGetSubString(hex, 7, 8)) & 0x3F) << 6 + 
 +        (integer)("0x" + llGetSubString(hex, 10, 11)) & 0x3F;
 +}
 +
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasOrdCompare(string A, string B) {
 +    if(A == "" && B != "") return 1;
 +    if(A != "" && B == "") return -1;
 +    if(A == "" && B == "") return 0;
 +    
 +    integer i = Ord(llToUpper(llGetSubString(A, 0, 0)));
 +    integer j = Ord(llToUpper(llGetSubString(B, 0, 0)));
 +    
 +    if(i < j) return 1;
 +    if(i > j) return -1;
 +    
 +    return wasOrdCompare(
 +        llGetSubString(A, 1, -1), 
 +        llGetSubString(B, 1, -1)
 +    );
 +}
 +
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasBinarySearch(list data, string item) {
 +    integer l = llGetListLength(data);
 +    if(l == 0) return "";
 +    integer m = l/2;
 +    integer comp = wasOrdCompare(llList2String(data, m), item);
 +    if(comp == 0) return item;
 +    if(comp == 1) {
 +        data = llDeleteSubList(data, 0, m);
 +        jump continue;
 +    }
 +    data = llDeleteSubList(data, m, -1);
 +@continue;
 +    return wasBinarySearch(data, item);
 +}
 +</code>
 +====== Day Of Week ======
 +
 +Adapted from [[http://www.scriptol.com/programming/sources/dow.sol|Doomsday]].
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasDayOfWeek(integer d, integer m, integer y) {
 +    if(m<3) {
 +        m+=13;
 +        --y;
 +        jump done;
 +    }
 +    ++m;
 +@done;
 +    return llList2String([ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ], 
 +                (d + ((26 * m) / 10) + y + (y / 4) - (y / 100) + (y / 400) + 5) % 7);
 +}
 +</code>
 +
 +====== Modular Exponentiation ======
 +
 +===== With O(n) Complexity =====
 +
 +Computes $a^{b} \mod{m}$ with $O(b)$ complexity.
 +
 +<code lsl2>
 +integer wasModExp(integer a, integer b, integer m) {
 +    integer res = 1;
 +    integer i = b;
 +    do {
 +        res *= a;
 +        res %= m;
 +    } while(--i>0);
 +    return res % m;
 +}
 +</code>
 +
 +===== With O(log(n)) Complexity =====
 +
 +$$
 +a^{b} =
 +\left\{
 +  \begin{array}{ll}
 +    (a^{2})^{\frac{b}{2}}  & \mbox{if } b \mbox{ is even and } b > 0 \\
 +    a*(a^{2})^{\frac{b-1}{2}} & \mbox{if } b \mbox{ is odd} \\
 +    1 & \mbox{if } b = 0
 +  \end{array}
 +\right.
 +$$
 +
 +<code lsl2>
 +integer wasModExp(integer a, integer b, integer m) {
 +    integer x = 1;
 +    integer y = a;
 +    while (b > 0) {
 +        if (b % 2 == 1) x = (x*y) % m;
 +        y = (y*y) % m;
 +        b /= 2;
 +    }
 +    return x % m;
 +}
 +</code>
 +
 +====== Check if Number is Prime ======
 +
 +Uses [[fuss:lsl#modular_exponentiation|wasModExp]] to calculate $a^b \mod{m}$, takes as input a number ''n'' and a number of trials to run ''k'' and returns ''0'' if ''n'' is composite or ''1'' if ''n'' is probably prime.
 +
 +Example:
 +<code lsl2>
 +wasIsPrime(7, 1);
 +</code>
 +returns ''1''.
 +
 +Example:
 +<code lsl2>
 +wasIsPrime(24, 1);
 +</code>
 +returns ''0''.
 +
 +Uses Fermat's little theorem which states that if $p$ is prime and $1\leq a\leq p$, then $a^{p-1} \equiv 1 (\mod{p})$.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasIsPrime(integer n, integer k) {
 +    if(k==0) return 1;
 +    integer a = 1+(integer)llFrand(n-1);
 +    if(wasModExp(a, n-1, n) != 1) return 0;
 +    return wasIsPrime(n, --k);
 +}
 +</code>
 +
 +====== Date to Unix Timestamp ======
 +
 +From the Linux kernel, converts ''YYYY-MM-DD HH:MM:SS'' to Unix timestamp.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasDateTimeToStamp(
 +    integer year,
 +    integer month,
 +    integer day,
 +    integer hour,
 +    integer minute,
 +    integer second
 +    ) {
 +    month -= 2;
 +    if (month <= 0) {
 +        month += 12;
 +        --year;
 +    }
 +    return 
 +    (
 +        (((year / 4 - year / 100 + year / 400 + (367 * month) / 12 + day) +
 +                year * 365 - 719499
 +            ) * 24 + hour
 +        ) * 60 + minute
 +    ) * 60 + second;
 +}
 +</code>
 +
 +====== Activity Spinner ======
 +
 +The following function will rotate the spinner once and can be used separately in scripts.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list spinChars = [ "↑", "↗", "→", "↘", "↓", "↙", "←", "↖" ];
 +string wasSpin() {
 +    string text = llList2String(llGetLinkPrimitiveParams(LINK_THIS, [PRIM_TEXT]), 0);
 +    do {
 +        string tok = llGetSubString(text, llStringLength(text)-1, llStringLength(text)-1);
 +        if(!~llListFindList(spinChars, (list)tok) && tok != "\n" && tok != "[" && tok != "]") jump show;
 +        text = llDeleteSubString(text, llStringLength(text)-1, llStringLength(text)-1);
 +    } while(llStringLength(text));
 +@show;
 +    string next = llList2String(spinChars,  0);
 +    spinChars = llDeleteSubList(spinChars, 0, 0);
 +    spinChars += next;
 +    return "[" + next + "]";
 +}
 +</code>
 +
 +To make the spinner move one step forward, call:
 +<code lsl2>
 +spin()
 +</code>
 +which returns [↗] where ↗ is the next position of the arrow.
 +
 +You can find some collected sets of spinners on the [[assets/databases/character_art/spinners|spinner asset page]] which you can set the ''spinChars'' list to.
 +====== Decompose a Number into Prime Factors ======
 +
 +This is the standard Euler division method which is preferable to the sieve method since the sieve method would require to allocate a list of integers that would surpass the memory constraints of LSL scripts.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasPrimeFactors(integer n) {
 +    list result = [];
 +    integer div = 2;
 +    do {
 +        if(n%div == 0) {
 +            n = n/div;
 +            result += div;
 +            jump continue;
 +        }
 +        ++div;
 +@continue;
 +    } while(n != 1);
 +    return result;
 +}
 +</code>
 +
 +====== Randomly Rotate Times by Angle ======
 +
 +When this function is called, it will rotate the primitive it is in ''times'' times, by random combinations of the vector ''<x,y,z>'' where ''x'', ''y'' and ''z'' can be either ''angle'' or ''0''. A demonstration of the functionality can be found on the [[secondlife:dice|dice page]].
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +wasRandomRotate(integer times, float angle) {
 +    if(times <= 0) return;
 +    integer x = (integer)llFrand(2);
 +    integer y = (integer)llFrand(2);
 +    integer z = (integer)llFrand(2);
 +    llSetRot(llGetRot()*llEuler2Rot(<x,y,z>*angle*DEG_TO_RAD));
 +    wasRandomRotate(--times, angle);
 +}
 +</code>
 +
 +If rotation is desired on one single axis only, the following modification will do that:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +wasRandomRotate(integer times, float angle) {
 +    if(times <= 0) return;
 +    integer x = (integer)llFrand(2);
 +    if(x == 1) jump rotate;
 +    integer y = (integer)llFrand(2);
 +    if(y == 1) jump rotate;
 +    integer z = (integer)llFrand(2);
 +@rotate;
 +    llSetRot(llGetRot()*llEuler2Rot(<x,y,z>*angle*DEG_TO_RAD));
 +    wasRandomRotate(--times, angle);
 +}
 +</code>
 +
 +====== Count Consecutive Characters in Strings ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasConsecutiveCharacterMatches(string hay, string needle) {
 +    if(llStringLength(hay) == 0 || llStringLength(needle) == 0) return 0;
 +    integer count = 0;
 +    string a = llGetSubString(hay, 0, 0);
 +    string b = llGetSubString(needle, 0, 0);
 +    if(a == b) {
 +        ++count;
 +        hay = llDeleteSubString(hay, 0, 0);
 +        needle = llDeleteSubString(needle, 0, 0);
 +        jump continue;      
 +    }
 +    if(llStringLength(hay) > llStringLength(needle)) {
 +        hay = llDeleteSubString(hay, 0, 0);
 +        jump continue;
 +    }
 +    if(llStringLength(hay) < llStringLength(needle)) {
 +        needle = llDeleteSubString(needle, 0, 0);
 +        jump continue;
 +    }
 +    hay = llDeleteSubString(hay, 0, 0);
 +    needle = llDeleteSubString(needle, 0, 0);
 +@continue;
 +    return count + wasConsecutiveCharacterMatches(hay, needle);
 +}
 +</code>
 +
 +====== String to Vector or Rotation ======
 +
 +<code lsl2>
 +vector v = (vector)"<1, 2, 3>";
 +rotation r = (rotation)"<1, 2, 3, -1>";
 +</code>
 +
 +
 +====== List to Vector or Rotation ======
 +
 +<code lsl2>
 +list a = [ "3", "2", "1" ];
 +vector v = (vector)("<" + llList2CSV(a) + ">");
 +
 +a = [ "3", "2", "1", "-1" ];
 +rotation r = (rotation)("<" + llList2CSV(a) + ">");
 +</code>
 +
 +====== Mixed-Type List to Type ======
 +
 +The following function converts a list of mixed types to a list of type ''T''.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasCompoundToList(integer T, list compound) {
 +    if(llGetListLength(compound) == 0) return [];
 +    if(T == TYPE_FLOAT) return llList2Float(compound, 0) + 
 +        wasCompoundToList(T, llDeleteSubList(compound, 0, 0));
 +    if(T == TYPE_INTEGER) return llList2Integer(compound, 0) + 
 +        wasCompoundToList(T, llDeleteSubList(compound, 0, 0));
 +    if(T == TYPE_STRING) return llList2String(compound, 0) + 
 +        wasCompoundToList(T, llDeleteSubList(compound, 0, 0));
 +    if(T == TYPE_KEY) return llList2Key(compound, 0) + 
 +        wasCompoundToList(T, llDeleteSubList(compound, 0, 0));
 +    if(T == TYPE_VECTOR) return llList2Vector(compound, 0) + 
 +        wasCompoundToList(T, llDeleteSubList(compound, 0, 0));
 +    if(T == TYPE_ROTATION) return llList2Rot(compound, 0) + 
 +        wasCompoundToList(T, llDeleteSubList(compound, 0, 0));
 +    return wasCompoundToList(T, llDeleteSubList(compound, 0, 0));
 +}
 +</code>
 +
 +For example, suppose you have a compound list:
 +
 +<code lsl2>
 +list a = [ 0, NULL_KEY, 3.546 ];
 +</code>
 +
 +and you want to convert all the values to a float type. You would then do:
 +
 +<code lsl2>
 +llOwnerSay(
 +            llDumpList2String(
 +                wasCompoundToList(TYPE_FLOAT, a),
 +            ",")
 +        );
 +</code>
 +
 +which would say to the owner:
 +
 +<code lsl2>
 +0.000000,0.000000,3.546000
 +</code>
 +
 +An example of the usefulness of the function can be found on the [[secondlife:donation_jar:ample_donation_jar|ample donation jar page]] where ''llSetPayPrice'' wants a list of integers to be supplied as the second argument for the buttons when the call to ''llParseString2List'' returns a list with all the elements of type ''string''.
 +
 +====== Vector or Rotation To List of Type ======
 +
 +''wasCompoundToType'' takes as parameters a type in variable ''T'' and either a ''vector'' or ''rotation'' in variable ''compound'' and returns a ''list'' with all components of ''compound'' set to type ''T''.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasCompoundToType(integer T, list compound) {
 +    integer S = llGetListEntryType(compound, 0);
 +    if(S != TYPE_VECTOR && S != TYPE_ROTATION) return [];
 +    list a = llParseString2List((string)compound, ["<", ",", ">"], []);
 +    compound = [];
 +    do {
 +        if(T == TYPE_FLOAT) 
 +            compound += llList2Float(a, 0);
 +        if(T == TYPE_INTEGER) 
 +            compound += llList2Integer(a, 0);
 +        if(T == TYPE_STRING) 
 +            compound += llList2String(
 +                llParseString2List((string)a, [" "], [])
 +            , 0);
 +        a = llDeleteSubList(a, 0, 0);
 +    } while(llGetListLength(a) != 0);
 +    return compound;
 +}
 +</code>
 +
 +This is useful in situations where you would like to retrieve the components of a ''vector'' or ''rotation'' without having to write multiple lines to convert each component. For example, after the following sequence:
 +
 +<code lsl2>
 +vector p = <23.499, 134, 39.048245>;
 +list intList = wasCompoundToType(TYPE_INTEGER, [p]);
 +</code>
 +
 +the list ''intList'' will contain a list of integers from the components of vector ''p'' (''p.x'', ''p.y'' and ''p.z''), with the mantissa removed (exactly what ''llFloor'' does).
 +
 +Similarly, after the sequence:
 +<code lsl2>
 +vector p = <23.499, 134, 39.048245>;
 +list floatList = wasCompoundToType(TYPE_FLOAT, [p]);
 +</code>
 +
 +the list ''floatList'' will contain a list of floats from the components of vector ''p'' (see below).
 +
 +When a string type is requested:
 +<code lsl2>
 +vector p = <23.499, 134, 39.048245>;
 +list truncatedStringList = wasCompoundToType(TYPE_STRING, [p]);
 +</code>
 +
 +the list ''truncatedStringList'' will contain a list strings with the mantissa truncated to ''6'' digits. In LSL, all floats get truncated to ''6'' decimal places when converted to strings. Note that the previous call to ''wasCompoundToType'' requesting ''TYPE_FLOAT'' will return a list containing floats with all decimal places (useful for computations).
 +
 +If any type other than ''vector'' or ''rotation'' is passed to ''wasCompoundToType'', the empty list will be returned. If a conversion to any type other than the ones implemented by ''wasCompoundToType'' (string, float or integer) are not meaningful (ie: converting a float to a key), the empty list will be returned.
 + 
 +The following is an example from [[secondlife:jump]], where string concatenation is supposed to build an SLURL out of a positional vector:
 +<code lsl2>
 +string slurl = llGetRegionName() + "/"
 +  llDumpList2String(
 +    llList2ListStrided(llParseString2List((string)llGetPos(), ["<", " ", ",", ".", ">"], []), 0, -1, 2)
 +  "/")
 +</code>
 +
 +and so is this:
 +<code lsl2>
 +vector p = llGetPos();
 +string slurl = llGetRegionName() + "/"
 +  (string)((integer)p.x) + "/"
 +  (string)((integer)p.y) + "/"
 +  (string)((integer)p.z);
 +</code>
 +
 +However, instead of writing a really large line and to avoid allocating a new variable ''p'', we can instead write:
 +<code lsl2>
 +string slurl = llGetRegionName() + "/"
 +  llDumpList2String(
 +    wasCompoundToType(TYPE_INTEGER, [llGetPos()]), 
 +  "/")
 +</code>
 +
 +
 +
 +
 +====== Merge Sort ======
 +
 +Example:
 +<code lsl2>
 +wasMergeSort([20, 5, 1, 11, 12, 32, 8, 3, 4, 7])
 +</code>
 +
 +Returns the list:
 +<code>
 +1 3 4 5 7 8 11 12 20 32
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasMergeSortMerge(list left, list right) {
 +    if(!llGetListLength(left) || !llGetListLength(right)) return left+right;
 +    integer l = llList2Integer(left, 0);
 +    integer r = llList2Integer(right, 0);
 +    if(l <= r) return l + wasMergeSortMerge(llDeleteSubList(left, 0, 0), right);
 +    return r + wasMergeSortMerge(left, llDeleteSubList(right, 0, 0));
 +}
 +
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasMergeSort(list input) {
 +    integer d = llGetListLength(input);
 +    if(d <= 1) return input;
 +    integer m = d/2;
 +    return wasMergeSortMerge(
 +        wasMergeSort(
 +            llList2List(input, 0, m-1)
 +        ), 
 +        wasMergeSort(
 +            llList2List(input, m, d)
 +        )
 +    );
 +}
 +</code>
 +
 +====== Private Channel Key-Hash ======
 +
 +Extremely useful, taken from the ''llDialog'' wiki page:
 +
 +**Inline Usage**
 +<code lsl2>
 +integer comChannel = ((integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
 +</code>
 +
 +it hashes keys to integers and makes a great method of generating private, negative channels.
 +
 +===== Simplified Alternative =====
 +
 +Suppose we have an arbitrary UUID such as:
 +
 +<code>
 +84c08dd4-7009-6b21-4645-d0d3ed5265df
 +</code>
 +
 +We know that integer types start from ''0x80000000'' which represents ''−2,147,483,648''. So we extract 7 characters off the UUID:
 +<code>
 +84c08dd
 +</code>
 +
 +Now, we know that we want a negative number, so we concatenate ''0x8'' to ''84c08dd'' and obtain:
 +<code>
 +0x884c08dd
 +</code>
 +
 +and casting that string to an integer, this will give us ''-2008282915'' which is a valid negative channel.
 +
 +Now we test the base-cases:
 +
 +  * Suppose that the key supplied is the ''NULL_KEY'' which is ''00000000-0000-0000-0000-000000000000''. If we extract the first ''7'' characters, we obtain ''0000000''. When we prefix ''0x8'' to the start, we obtain ''0x80000000'' which represents the smallest representable integer type ''−2147483648''.
 +  * Suppose that the key supplied is a maximal key, such that all positions are set to ''F'' which gives us a key such as ''FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF''. If we extract the first ''7'' characters, we obtain ''FFFFFFF''. When we prefix ''0x8'' to the start, we obtain ''0x8FFFFFFF'' which gives us '' -1879048193''.
 +
 +Thus, regardless what the first ''7'' characters of an UUID is, in LSL we will still obtain a negative number, in the interval $[-2147483648, -1879048193]$ with a total of $268435455$ possible numbers.
 +
 +The code is a one liner:
 +
 +<code lsl2>
 +integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
 +</code>
 +
 +and reduces the operations performed by the previous version.
 +
 +Since there are ''16'' types of characters that we can choose, and since we select ''7'' characters in a row, the probability to select one particular sequence from all sequences is given by:
 +
 +$$
 +\frac{1}{{{16}\choose{7}}} = \frac{1}{\frac{16!}{(16-7)!7!}} = \frac{1}{11440} \approx 0.000087412587413
 +$$
 +
 +which would probabilistically imply that after $11440$ UUIDs one could expect a collision.
 +
 +Now this is very decent, in fact it is pretty redundant that we could have chosen a smaller nibble to work with. Suppose that you have an object listening on a region to an avatar using this hash. Probabilistically, you would need another $11439$ avatars on the same region to expect collisions. Given that a region can hold at best, 20, 40, or on OpenSim's testing bed 100 avatars at the same time, you are still way below the probability of a collision.
 +
 +====== Count Elements in List Excluding Certain Elements ======
 +
 +The following function returns the number of elements in a list excluding a certain string. For example, given a list such as:
 +
 +<code lsl2>
 +list input = ["a", 1, 0.45, "", 43];
 +</code>
 +when called with:
 +<code lsl2>
 +wasListCount(input, [""]);
 +</code>
 +the function will return ''4''.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasListCountExclude(list input, list exclude) {
 +    if(llGetListLength(input) == 0) return 0;
 +    if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) 
 +        return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
 +    return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
 +}
 +</code>
 +
 +====== Merge Lists ======
 +
 +The following function will merge two lists ''l'' and ''m'', on the marker ''merge'', given the positions of the merger list ''m''.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasListMerge(list l, list m, string merge) {
 +    if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return [];
 +    string a = llList2String(m, 0);
 +    if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge);
 +    return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge);
 +}
 +</code>
 +
 +To understand this better, suppose you have a table using the lists ''a'' and ''b'' above and the indices of the elements:
 +
 +^ Index  ^ 0  ^ 1  ^ 2 ^
 +| List a | 1  | "" | 2 |
 +| List b | "" | b  |   |
 +
 +The algorithm goes through list ''a'':
 +  * If the value of the element of list ''b'' is set to the value of ''merge'', it will prefer the element of ''a''.
 +    * Otherwise, it will prefer the value of the element of ''b''.
 +  * If the value of ''b'' is not set, it will prefer the element of ''a''.
 +
 +===== Example =====
 +
 +Suppose we have the following lists:
 +<code lsl2>
 +list a = [ 1, "", 2 ];
 +list b = [ "", "b" ]; 
 +</code>
 +when calling the function with:
 +<code lsl2>
 +wasListMerge(a, b, "");
 +</code>
 +it will return the list:
 +<code lsl2>
 +[ 1, b, 2 ];
 +</code>
 +
 +===== Counter-Example =====
 +
 +Suppose we have the following lists:
 +<code lsl2>
 +list a = [ 1, "", 2 ];
 +list b = [ "a", "b" ]; 
 +</code>
 +when calling the function with:
 +<code lsl2>
 +wasListMerge(a, b, "");
 +</code>
 +it will return the list:
 +<code lsl2>
 +[ "a", "b", 1, 2 ];
 +</code>
 +
 +===== Practical Example ========
 +
 +Suppose that we have a menu list that is passed to ''llDialog'', such as:
 +
 +<code lsl2>
 +list menu = [ "Blue", "Red", "Green", "White", "Black" ];
 +</code>
 +
 +and we want to insert ''<= Back'' and ''Next =>'' buttons (in style) so that we have something like:
 +
 +| Black                         |
 +| Red         | Green | White       |
 +| ''<= Back'' | Blue  | ''Next =>'' |
 +
 +with the ''<= Back'' and ''Next =>'' buttons (nicely) displayed on the left-most part of the menu, respectively right-most part, then we would call the function as:
 +
 +<code lsl2>
 +wasListMerge(menu, ["<= Back", "", "Next =>"], "");
 +</code>
 +
 +The empty string between the buttons indicates that an element from the ''menu'' list can be placed there.
 +
 +====== Endless Menu with Custom Buttons ======
 +
 +Using the two functions defined previously [[fuss:lsl#count_elements_in_list_excluding_certain_elements|wasListCountExclude]] and [[fuss:lsl#merge_lists|wasListMerge]] we can build an endless menu with customizable buttons. For an in-depth explanation on the usage of this function, please see the [[secondlife:pretty_menu_system|pretty menu system]].
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasMenuIndex = 0;
 +list wasDialogMenu(list input, list actions, string direction) {
 +    integer cut = 11-wasListCountExclude(actions, [""]);
 +    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
 +        ++wasMenuIndex;
 +        jump slice;
 +    }
 +    if(direction == "<" && wasMenuIndex-1 >= 0) {
 +        --wasMenuIndex;
 +        jump slice;
 +    }
 +@slice;
 +    integer multiple = wasMenuIndex*cut;
 +    input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex);
 +    input = wasListMerge(input, actions, "");
 +    return input;
 +}
 +</code>
 +
 +This refines the previously outdated method (which has now been removed) by allowing custom buttons to be inserted and processed.
 +
 +====== Point Generation ======
 +
 +The following sub-routines generate points within various bodies and shapes.
 +
 +{{indexmenu>fuss/algorithms/geometry/point_generation}}
 +====== Key-Value Data ======
 +
 +The following functions allow processing of key-value data. An overview is provided on the [[.:data_structures:key-value_pairs|key-value data page]].
 +
 +{{indexmenu>fuss/data_structures/key-value_pairs}}
 +====== Empty String or Blank ======
 +
 +Similar to ''isspace'' in C, but returns ''TRUE'' for the empty strings as well.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasIsSpace(string input) {
 +    if(input == "") return TRUE;
 +    list split = llParseString2List(llEscapeURL(input), ["%"], []);
 +    do {
 +        string code = llList2String(split, 0);
 +        if(code != "0A" && code != "20") return FALSE;
 +        split = llDeleteSubList(split, 0, 0);
 +    } while(llGetListLength(split));
 +    return TRUE;
 +}
 +</code>
 +
 +====== Sentence Sub-Sequence Match ======
 +
 +The function expects ''input'' to be a written sentences where words are separated by spaces. Returns ''TRUE'' if the sub-sentence ''match'' is to be found in the sentence ''input'', or ''FALSE'' otherwise.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasMatchSubSentence(string input, string match) {
 +    list data = llParseString2List(input, [" "], []);
 +    do {
 +        if(llList2String(data, 0) == match) return TRUE;
 +        data = llDeleteSubList(data, 0, 0);
 +    } while(llGetListLength(data));
 +    return FALSE;
 +}
 +</code>
 +
 +====== Relative Positions ======
 +
 +{{fuss_lsl_relative_positions.png}}
 +
 +Suppose we have two points in space, $A$ and $B$. First, we pick either object as a point of reference, say $A$. Then we calculate:
 +
 +$$
 +T=\frac{-(A_{position}-B_{position})}{A_{rotation}}
 +$$
 +
 +Which gives us a translation from point $A$ to point $B$. Finally, to get the new position of object $B$, we perform:
 +
 +$$
 +B_{position} = A_{position}+T*A_{rotation}
 +$$
 +
 +In LSL terms, if we have an object ''A'' at:
 +<code lsl2>
 +vector A = <xa, ya, za>
 +</code>
 +We also know the rotation of object ''A'':
 +<code lsl2>
 +rotation RA = <xar, yar, zar, sar>;
 +</code>
 +
 +Next, let's say we the object ''B'' at:
 +<code lsl2>
 +vector B = <xb, yb, zb>
 +</code>
 +
 +Now, if we rotate and move object ''A'', then we want to determine the new position of object ''B'' relative to object ''A''. We also want to take into account the rotation of object ''A''.
 +
 +In order to do that, we calculate the translation vector ''T'':
 +<code lsl2>
 +vector T = -(A-B)/RA;
 +</code>
 +which we can store.
 +
 +Later, we can set the position of object ''B'' to:
 +<code lsl2>
 +vector NEW_B = A + T*RA.
 +</code>
 +
 +This is used in the [[secondlife:poseball_system|poseball system]] in order to place the poseball relative to the sofa. Intuitively, we cannot know what final rotation and position the sofa will have when a customer places it somewhere. Thus, we only store the relative translation ''T'' and perform ''A+T'' so that the sofa can be rotated and moved into any position.
 +====== Relative Rotations ======
 +
 +{{fuss_lsl_relative_rotations.png}}
 +
 +
 +Suppose we have a reference rotation in quaternions $Q_{reference}$ and a final rotation $Q_{final}$. We want to find out what relative rotation $Q_{transition}$ we have to apply to $Q_{reference}$ in order to get $Q_{final}$.
 +
 +The problem can be solved easily since we know that the final rotation will be the original reference rotation composed with "some" rotation that we do not know yet that we call $Q_{transition}$.
 +
 +$$
 +Q_{reference} * Q_{transition} = Q_{final}
 +$$
 +
 +This $Q_{transition}$ represents a difference between the original reference rotation and the final rotation that we want to achieve.
 +
 +This difference, a transition rotation, is given by:
 +$$
 +Q_{transition}=Q_{reference}^{-1}*Q_{final}
 +$$
 +
 +To obtain $Q_{reference}^{-1}$ we either negate the real part component or we can just negate the $x,y,z$ components. 
 +
 +Suppose that: 
 +
 +$$
 +Q_{reference} = <Q_{reference}.x, Q_{reference}.y, Q_{reference}.z, Q_{reference}.s>
 +$$
 +
 +then:
 +$$
 +Q_{reference}^{-1} = <Q_{reference}.x, Q_{reference}.y, Q_{reference}.z, -Q_{reference}.s>
 +$$
 +
 +or:
 +
 +$$
 +Q_{refernece}^{-1} = <-Q_{reference}.x, -Q_{reference}.y, -Q_{reference}.z, Q_{reference}.s>
 +$$
 +
 +Finally, we rotate the object with:
 +
 +$$
 +Q_{final} = Q_{reference}*Q_{transition}
 +$$
 +
 +In LSL terms, suppose that we have a reference rotation:
 +<code lsl2>
 +rotation RA = <a.x, a.y, a.z, a.s>
 +</code>
 +and suppose we have the final rotation:
 +<code lsl2>
 +rotation RB = <b.x, b.y, b.z, b.s>
 +</code>
 +we first calculate the transition rotation:
 +<code lsl2>
 +rotation T = <RA.x,RA.y,RA.z,-RA.s> * RB;
 +</code>
 +and we set the new object's rotation to:
 +<code lsl2>
 +llSetRot(RA * T);
 +</code>
 +
 +This was used for the [[secondlife:poseball_system|poseball system]] project in order to rotate the poseballs into place regardless of the rotation of the sofa. After the poseballs are rezzed relative to the sofa (see [[fuss:lsl?&#relative_positions]]) they will have no rotation. Thus, we need to set the rotation to ''A*T'' so that the poses match the rotation of the sofa.
 +
 +====== Ord ======
 +
 +Returns an integer with the Unicode code of the first character in ''chr''.
 +
 +<code lsl2>
 +// Ord() function, written by Pedro Oval, 2010-05-28
 +// Inlined by Wizardry and Steamworks
 +integer Ord(string chr) {
 +    if (chr == "") return 0;
 +    string hex = llEscapeURL(chr);
 +    if (llGetSubString(hex, 0, 0) != "%"
 +        return llBase64ToInteger("AAAA"
 +            llStringToBase64(llGetSubString(chr, 0, 0)));
 +    integer b = (integer)("0x" + llGetSubString(hex, 1, 2));
 +    if (b < 194 || b > 244) return b;
 +    if (b < 224) return ((b & 0x1F) << 6) | 
 +        (integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F;
 +    if (b < 240) return (b & 0x0F) << 12 + 
 +        ((integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F) << 6 + 
 +        (integer)("0x" + llGetSubString(hex, 7, 8)) & 0x3F;
 +    return (b & 0x07) << 18 + 
 +        ((integer)("0x" + llGetSubString(hex, 4, 5)) & 0x3F) << 12 + 
 +        ((integer)("0x" + llGetSubString(hex, 7, 8)) & 0x3F) << 6 + 
 +        (integer)("0x" + llGetSubString(hex, 10, 11)) & 0x3F;
 +}
 +</code>
 +
 +====== Binary Trees and Binary Search Trees ======
 +
 +{{indexmenu>secondlife:binary_trees}}
 +
 +====== llListFindList and Keys ======
 +
 +Suppose you store keys as strings in a list called ''aList'' by reading a notecard. When using ''llListFindList'', the ''llDetectedKey'' call has to be casted to ''string'' before checking if the detected key is in the access list ''aList''.
 +
 +<code lsl2>
 +if (llListFindList(aList, (list)((string)llDetectedKey(0))) == -1) return;
 +</code>
 +
 +
 +====== Event Handler Order ======
 +
 +^ Event                          ^ Handler State Machine      ^
 +| Attach Object (from inventory) | ->''on_rez'' -> ''attach'' (''key id'' := agent key) -> ''state_entry'' |
 +| Attach Object (from ground)    | ->''attach'' (''key id'' := agent key) -> ''state_entry'' |
 +| Detach Object (from inventory) | ->''attach'' (''key id'' := ''NULL_KEY'') -> ''state_entry''  |
 +| Log-in with item attached      | ->''on_rez'' -> ''attach'' -> ''state_entry'' |
 +| Logout with item attached      | $\phi$ (script retains last state) |
 +| State-change                   | ->''state_exit''->''state_entry''  |
 +| Rez in-world                   | -> ''on_rez'' -> ''state_entry'' |
 +
 +====== LID™ ======
 +
 +As explained by the [[secondlife:lid|LID™ page]], the following function can be used to detect script resets or : 
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +LID(key id) {
 +    if(id != NULL_KEY && llGetAttached() != 0) {
 +        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", (string)((integer)wasKeyValueGet("login", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))+1), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
 +        llResetScript();
 +    }
 +    llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "-1", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
 +}
 +</code>
 +
 +has to be called in the ''attach'' event handler:
 +
 +<code lsl2>
 +    attach(key id) {
 +        LID(id);
 +    }
 +</code>
 + 
 +
 +
 +====== RGB to Color ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +vector wasRGBToColor(vector rgb) {
 +    return <rgb.x / 255, rgb.y / 255, rgb.z / 255>;
 +}
 +
 +</code>
 +
 +====== Color to RGB ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +vector wasColorToRGB(vector color) {
 +    return <color.x * 255, color.y * 255, color.z * 255>;
 +}
 +
 +</code>
 +
 +====== Center String for Dialog Message ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasDialogCenterASCII(string in) {
 +    integer l = llStringLength(in);
 +    if(l > 47) return in;
 +    integer s = (47-l)/2;
 +    string pad = "";
 +    do {
 +        pad += " ";
 +    } while(--s>0);
 +    return pad + in + pad;
 +}
 +</code>
 +
 +====== RVL Trap Relay ======
 +
 +''wasTrapCommand'' takes as argument the ''id'' of the object sending the command and the command to process in ''message'' as per the [[http://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Love_Relay/Specification|RLV relay specification]].
 +
 +Very few changes were performed, mostly jump-table optimizations and using the ''list'' as a queue, deleting commands and looping until the list is emptied and merging several functions (the original function that this code is based on is called ''executeRLVCommand'').
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +//      Original by: By Marine Kelley, Maike Short and Felis Darwin      //
 +///////////////////////////////////////////////////////////////////////////
 +wasTrapCommand(key id, string message) {
 +    list tokens = llParseString2List (message, [","], []);
 +    if (llGetListLength (tokens) != 3) return;
 +    string cmd_id = llList2String(tokens, 0);
 +    if (llList2Key(tokens, 1) != llGetOwner ()) return;
 +    list commands = llParseString2List(llList2String(tokens, 2), ["|"], []);
 +    do {
 +        string command = llList2String(commands, 0);
 +        
 +        // relay metacommands
 +        if(command == "!release") {
 +            do {
 +                string restriction = llList2String(_relayRestrictions, 0);
 +                if(llGetSubString(restriction, 0, 0) != "@") jump next;
 +                llOwnerSay(restriction + "=y");
 +@next;
 +                _relayRestrictions = llDeleteSubList(_relayRestrictions, 0, 0);
 +            } while(llGetListLength(_relayRestrictions) != 0);
 +            llShout(-1812221819, cmd_id + "," + (string)id + "," + command + "," + "ok");
 +            return;
 +        }
 +        
 +        if (command == "!version") {
 +            llShout(-1812221819, cmd_id + "," + (string)id + "," + command + "," + "1100");
 +            jump continue;
 +        }
 +        
 +        // standard commands
 +        if (llGetSubString(command, 0, 0) != "@") jump continue;
 +        
 +        list commandTokens = llParseString2List (command, ["="], [""]);
 +        string behav = llList2String(commandTokens, 0);
 +        string param = llList2String(commandTokens, 1);
 +        integer idx = llListFindList(_relayRestrictions, [behav]);
 +        
 +        if ((param == "n" || param == "add") && idx == -1) {
 +            llOwnerSay(behav + "=n");
 +            _relayRestrictions += [behav];
 +            jump acknowledge;
 +        }
 +        if ((param == "y" || param == "rem") && idx != -1) {
 +            llOwnerSay(llList2String(_relayRestrictions, idx) + "=y");
 +            _relayRestrictions = llDeleteSubList(_relayRestrictions, idx, idx);
 +            jump acknowledge;
 +        }
 +        llOwnerSay(command);
 +@acknowledge;
 +        llShout(-1812221819, cmd_id + "," + (string)id + "," + command + "," + "ok");
 +@continue;
 +        commands = llDeleteSubList(commands, 0, 0);
 +    } while(llGetListLength(commands) != 0);
 +}
 +</code>
 +
 +====== Elipse Circumference ======
 +
 +<code lsl2>
 +float wasElipseCircumference(float a, float b, integer precision) {
 +    float x = llListStatistics(LIST_STAT_MAX, [a, b]);
 +    float y = llListStatistics(LIST_STAT_MIN, [a, b]);
 +    float tol = llSqrt(llPow(.5, precision));
 +    if (precision * y < tol * x) return 4 * x;
 +    float s = 0;
 +    float m = 1;
 +    while (x - y > tol * y) {
 +        x = .5 * (x + y);
 +        y = llSqrt(x * y);
 +        m *= 2;
 +        s += m * llPow(x - y, 2);
 +    }
 +    return PI * (llPow(a + b, 2) - s) / (x + y);
 +}
 +</code>
 +
 +====== Point in Polygon ======
 +
 +Given a point, represented by a vector ''p'' and a set of points, represented by the list ''polygon'', the following function determines whether ''p'' is inside the polygon.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +//    www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasPointInPolygon(vector p, list polygon) {
 +    integer inside = FALSE;
 +    integer i = 0;
 +    integer nvert = llGetListLength(polygon);
 +    integer j = nvert-1;
 +    do {
 +        vector pi = llList2Vector(polygon, i);
 +        vector pj = llList2Vector(polygon, j);
 +        if (((pi.y > p.y) != (pj.y > p.y)) &&
 +            (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x)) {
 +            inside = !inside;
 +        }
 +        j = i++;
 +    } while(i<nvert);
 +    return inside;
 +}
 +</code>
 +
 +The function returns ''TRUE'' if ''p'' is inside the polygon and ''FALSE'' otherwise.
 +
 +====== Path Simplification Algorithm ======
 +
 +{{fuss_path_simplification_algorithm.gif}}
 +
 +Given a set of points in space, the following algorithm simplifies the path so that no distance between two points from the set is smaller than ''segment''. It does so recursively, one of the invariants being that at any point in time, the set to be returned does not contain any two points smaller than ''segment''. This makes the algorithm suitable for streams, in case the coordinates are streamed-in, one by one.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasPathSimplify(list path, float segment) {
 +    if(llGetListLength(path) == 0) return path;
 +    
 +    // grab first two points...
 +    vector P = wasStringToVector(llList2String(path, 0));
 +    path = llDeleteSubList(path, 0, 0);
 +    if(llGetListLength(path) == 0) return path + [P];
 +    vector Q = wasStringToVector(llList2String(path, 0));
 +    path = llDeleteSubList(path, 0, 0);
 +
 +    // if they exceed or equal the segment length
 +    if(llVecDist(P, Q) >= segment) {
 +        // save the previous one (P) and,
 +        // push the next one onto the stack (Q).
 +        // wend
 +        return [ P ] + wasPathSimplify([ Q ] + path, segment);
 +    }
 +    // otherwise, make the mean between them... 
 +    float dist = llVecDist(P, Q);
 +    // ...and push it back onto the stack
 +    // wend
 +    return wasPathSimplify([ P + ((dist/2)/dist) * (Q-P) ] + path, segment);
 +
 +</code>
 +
 +====== Partition a String ======
 +
 +Optimistically partitions a string ''data'' into partitions of at most length ''n'' and returns the resulting list.
 +
 +Given a string with value ''abc'', the following call:
 +<code>
 +llOwnerSay("Result: " + llDumpList2String(wasPartitionString("abc", 2), "|"));
 +</code>
 +
 +will return a list:
 +<code lsl2>
 +[ "ab", "c"]
 +</code>
 +
 +The function operates by divide and conquer and takes care to not return the empty string in the borderline case $length(data) \leq n$.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasPartitionString(string data, integer n) {
 +    string left = llGetSubString(data, 0, n-1);
 +    string right = llGetSubString(llDeleteSubString(data, 0, n-1), 0, -1);
 +    
 +    if(llStringLength(left) <= n && llStringLength(right) <= n) {
 +        if(llStringLength(left) == 0) return [ right ];
 +        if(llStringLength(right) == 0) return [ left ];
 +        return [left, right];
 +    }
 +
 +    if(llStringLength(right) <= n) 
 +        return wasPartitionString(left, n) + [ right ];
 +        
 +    if(llStringLength(left) <= n) 
 +        return [ left ] + wasPartitionString(right, n);
 +        
 +    return wasPartitionString(data, n);
 +}
 +</code>
 +
 +====== Repeat a String ======
 +
 +A call such as:
 +<code lsl2>
 +wasRepeatString("✪✪", " ", 4)
 +</code>
 +
 +will return the string:
 +<code lsl2>
 +✪✪ ✪✪ ✪✪ ✪✪
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasRepeatString(string in, string sep, integer num) {
 +    if(num == 0) return "";
 +    list i = llParseString2List(in, (list)sep, []);
 +    integer l = llGetListLength(i);
 +    if(l >= num) return llDumpList2String(llList2List(i, 0, l-(l-num)-1), sep);
 +    return wasRepeatString(in + sep + in, sep, num);
 +}
 +</code>
 +
 +====== Rotate a Linked Primitive around Other Linked Primitives ======
 +
 +{{fuss_lsl_rotate_around_linked_primitive.gif}}
 +
 +Sometimes it is useful to rotate a primitive around another linked primitive, for example, for creating a planetarium or gear mechanics where ''llTargetOmega'' cannot be used. This is actually easily accomplished, by using the equation of a circle and by minding the local and global reference systems.
 +
 +The function is to be used inside the primitive to roate and takes as parameter a link number, an angle to rotate by and a plane vector. These are explained as:
 +
 +  * the link number is the primitive to rotate around (in the animation above, the centre sphere representing the sun).
 +  * the angle is the angle in degrees to rotate by ($0 \ldots 360$) in the trigonometric sense.
 +  * the plane vector gives the plane in which to rotate ($x,y$, $x,z$, $y,z$) given here as normal vectors, as in, the plane defined by the axes $x$ and $y$ will be the plane vector ''<1, 1, 0>'' and similar for the other planes.
 +
 +The illustrated animation is created using the following loop:
 +<code lsl2>
 +default {
 +    state_entry() {
 +@repeat;
 +        integer a = 0;
 +        do {
 +            llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POS_LOCAL, wasRotateAroundLinkNumber(2, a, <1, 1, 0>)]);
 +        } while(++a != 360);
 +        jump repeat;
 +    }
 +}
 +</code>
 +
 +where the function ''wasRotateAroundLinkNumber'' takes as parameter the centre sphere link number ''2'', the angle ''a'' starting from ''0'' and the plane vector ''<1, 1, 0>'' meaning the $x, y$ plane and is defined as:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +// Returns a vector with the position of this linked primitive rotated   //
 +// angle degrees around a linked primitive with link number link_num, in //
 +// the plane given by the vector plane.                                  //
 +///////////////////////////////////////////////////////////////////////////
 +vector wasRotateAroundLinkNumber(integer link_num, float angle, vector plane) {
 +    vector rp = llList2Vector(
 +        llGetLinkPrimitiveParams(link_num, [PRIM_POSITION])
 +    , 0);
 +    vector mp = llGetPos();
 +    vector fp = ZERO_VECTOR;
 +    float r = 0;
 +    if((integer)plane.y && (integer)plane.z) {
 +        r = llVecDist(
 +            <0, mp.y, mp.z>,
 +            <0, rp.y, rp.z>
 +        );
 +        fp = <mp.x, rp.y + r * llCos(angle*DEG_TO_RAD), rp.z + r * llSin(angle*DEG_TO_RAD)>;
 +        jump set;
 +    }
 +    if((integer)plane.x && (integer)plane.y) {
 +        r = llVecDist(
 +            <mp.x, mp.y, 0>,
 +            <rp.x, rp.y, 0>
 +        );
 +        fp =  <rp.x + r * llCos(angle*DEG_TO_RAD), rp.y + r * llSin(angle*DEG_TO_RAD), mp.z>;
 +        jump set;
 +    }
 +    if((integer)plane.x && (integer)plane.z) {
 +        r = llVecDist(
 +            <mp.x, 0, mp.z>,
 +            <rp.x, 0, rp.z>
 +        );
 +        fp = <rp.x + r * llCos(angle*DEG_TO_RAD), mp.y, rp.z + r * llSin(angle*DEG_TO_RAD)>;
 +        jump set;
 +    }
 +@set;
 +    return fp - llGetRootPosition() / llGetRootRotation();
 +}
 +</code>
 +
 +The ''wasRotateAroundLinkNumber'' first computes the [[fuss:lsl#planar_or_grid_distance|planar distance]] from the primitive to rotate to the link number by projecting onto the plane, then it computes the new coordinates in the plane using the circle equation by minding the relative distance from the root primitive and finally converts the global coordinates into local coordinates and rotates the primitive.
 +
 +Here is a linked build where the function may be useful:
 +
 +{{fuss_lsl_rotate_around_linked_primitive.png?512}}
 +
 +The yellow cylinder represents the root primitive, and pink ball is supposed to rotate around the blue rod at a given distance from the root primitive. This is accomplished with:
 +<code lsl2>
 +default {
 +    state_entry() {
 +@repeat;
 +        integer a = 0;
 +        do {
 +            llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POS_LOCAL, wasRotateAroundLinkNumber(3, a, <1, 0, 1>)]);
 +            llSleep(0.1);
 +        } while(++a != 360);
 +        jump repeat;
 +    }
 +}
 +</code>
 +
 +====== Avatar Speed ======
 +
 +The following is a list of avatar top-speeds:
 +
 +^ Type    ^ Top Speed ^
 +| Walking | $\approx 3.20 \frac{m}{s}$ |
 +| Running | $\approx 5.13 \frac{m}{s}$ |
 +| Flying  | $\approx 16 \frac{m}{s}$ |
 +
 +====== Explode and Implode a List ======
 +
 +Exploding a list for this function means, on every iteration, to take the centre of the list and move it to the front of the list:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasExplodeList(list l) {
 +    integer i = llGetListLength(l);
 +    if(i == 0) return [];
 +    return llList2List(l, i/2, i/2) + 
 +        wasExplodeList(llDeleteSubList(l, i/2, i/2));
 +}
 +</code>
 +
 +An example input is:
 +<code lsl2>
 +list a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
 +</code>
 +
 +and its corresponding output:
 +<code>
 +5,6,4,7,3,8,2,9,1
 +</code>
 +
 +Imploding a list means to take the extremes of the list and to group them at the front of the list:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasImplodeList(list l) {
 +    integer i = llGetListLength(l);
 +    if(i == 0) return [];
 +    return wasImplodeList(llDeleteSubList(l, i/2, i/2)) + 
 +        llList2List(l, i/2, i/2);
 +}
 +</code>
 +
 +An example input is:
 +<code lsl2>
 +list a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
 +</code>
 +
 +and its corresponding output:
 +<code>
 +1,9,2,8,3,7,4,6,5
 +</code>
 +
 +It is said that Leonhard Euler was asked during class to add-up all the numbers between and including $1$ and $100$ as a punishment. Instead of adding them up sequentially, he noticed that adding $1$ and $99$ will yield $100$, adding $2$ and $98$ would yield $100$ again, which allowed him to compute the calculation very fast by summing up the resulting $100$, making it an easier task. The previous example of ''wasImplodeList'' arranges the numbers such that two consecutive numbers add up to 10, with the exception being the midpoint.
 +
 +Depending on the number of elements in the input list, ''wasImplodeList'' has the following properties:
 +
 +  * In case of an odd number of elements in the input list, ''wasImplodeList'' arranges the numbers such that two successive numbers add-up to the same value and that the last number is always a multiple of the number that the previous successive numbers added-up to.
 +
 +  * In case of even number of elements in the input list, ''wasImplodeList'' arranges the numbers such that two successive numbers add-up toe the same value.
 +
 +====== Escape and Send POST Data ======
 +
 +This function takes as input some form data such as:
 +
 +<code>
 +name=CP-3&result=good&test=ok
 +</code>
 +
 +and returns a string with the escaped values:
 +<code>
 +name=CP%2D3&result=good&test=ok
 +</code>
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasKeyValueURIEscape(string data) {
 +    list i = llParseString2List(data, ["&", "="], []);
 +    list output = [];
 +    do {
 +        output += llList2String(i, 0) + "=" + llEscapeURL(llList2String(i, 1));
 +        i = llDeleteSubList(i, 0, 1);
 +    } while(llGetListLength(i) != 0);
 +    return llDumpList2String(output, "&");
 +}
 +
 +</code>
 +
 +The function can be used to send form data to a server-side script, for example:
 +<code lsl2>
 +string data = wasKeyValueURIEscape("name=CP-3&result=good&test=ok");
 +llHTTPRequest("http://server.name/script.php", [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], data);
 +</code>
 +will send ''data'' to a server using POST as form-data.
 +
 +On the server-side, if you are using PHP, you would have to decode the values:
 +<code php>
 +$name = urldecode($_POST['name']); // CP-3
 +$result = urldecode($_POST['result']); // good
 +$test = urldecode($_POST['test']); // ok
 +</code>
 +
 +====== Determining Whether Two Segments Intersect ======
 +
 +Given four points (''A'', ''B'', ''C'' and ''D'') defined in parametric form, such as:
 +<code lsl2>
 +vector A = <34.517483, 231.703461, 21.108919>;
 +vector B = <34.517483, 222.014297, 21.108919>; 
 +vector C = <29.423143, 226.800842, 21.108919>;
 +vector D = <38.186848, 226.800842, 21.108919>;
 +</code>
 +
 +In order to determine whether the segments $AB$ and $CD$ intersect, one would call the function as:
 +<code lsl2>
 +if(wasSegmentIntersect(A, B, C, D) == TRUE) {
 +    llOwnerSay("Segments AB and CD intersect.");
 +    return;
 +}
 +llOwnerSay("Segments AB and CD do not intersect.");
 +</code>
 +
 +''wasSegmentIntersect'' is defined as:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// determines whether the segment AB intersects the segment CD
 +integer wasSegmentIntersect(vector A, vector B, vector C, vector D) {
 +    vector s1 = <B.x - A.x, B.y - A.y, B.z - A.z>;
 +    vector s2 = <D.x - C.x, D.y - C.y, D.y - C.z>;
 + 
 +    float d = (s1.x * s2.y -s2.x * s1.y);
 + 
 +    if(d == 0) return FALSE;
 + 
 +    float s = (s1.x * (A.y - C.y) - s1.y * (A.x - C.x)) / d;
 +    float t = (s2.x * (A.y - C.y) - s2.y * (A.x - C.x)) / d;
 + 
 +    // intersection at <A.x + (t * s1.x), A.y + (t * s1.y), A.z + (t * s1.z)>;
 +    return (integer)(s >= 0 && s <= 1 && t >= 0 && t <= 1 && 
 +            A.z + t*(B.z - A.z) == C.z + s*(D.z - C.z));
 +}
 +</code>
 +====== Determine if Two Avatars or Objects Intersect (Collide) ======
 +
 +A collision between two avatars or objects in Second Life means that the two bounding boxes belonging to each avatar or object intersect.
 +
 +The function ''wasObjectIntersect'' takes as parameter two keys, either the keys of an avatars or an objects, and returns true if the objects or avatars collide.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// returns true if two objects intersect
 +integer wasObjectIntersect(key O1, key O2) {
 +    list o = llGetBoundingBox(O1);
 +    vector min = llList2Vector(o, 0);
 +    vector max = llList2Vector(o, 1);
 +    vector pos = llList2Vector(llGetObjectDetails(O1, [OBJECT_POS]), 0);
 +    vector o1_min = <pos.x + min.x, pos.y + min.y, pos.z + min.z>;
 +    vector o1_max = <pos.x + max.x, pos.y + max.y, pos.z + max.z>;
 +    o = llGetBoundingBox(O2);
 +    min = llList2Vector(o, 0);
 +    max = llList2Vector(o, 1);
 +    pos = llList2Vector(llGetObjectDetails(O2, [OBJECT_POS]), 0);
 +    vector o2_min = <pos.x + min.x, pos.y + min.y, pos.z + min.z>;
 +    vector o2_max = <pos.x + max.x, pos.y + max.y, pos.z + max.z>;  
 +    //intersect
 +    return (integer)(
 +        (
 +            (o1_min.x == o2_min.x && o1_min.y == o2_min.y && o1_min.z == o2_min.z) &&
 +            (o1_max.x == o2_max.x && o1_max.y == o2_max.y && o1_max.z == o2_max.z)
 +        ) || (
 +            (o1_min.x < o2_min.x && o1_min.y < o2_min.y && o1_min.z < o2_min.z) &&
 +            (o1_max.x < o2_max.x && o1_max.y < o2_max.y && o1_max.z < o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_max.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        ) || (
 +            (o1_min.x > o2_min.x && o1_min.y < o2_min.y && o1_min.z < o2_min.z) &&
 +            (o1_max.x > o2_max.x && o1_max.y < o2_max.y && o1_max.z < o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_min.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        ) || (
 +            (o1_min.x > o2_min.x && o1_min.y < o2_min.y && o1_min.z > o2_min.z) &&
 +            (o1_max.x > o2_max.x && o1_max.y < o2_max.y && o1_max.z > o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_min.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        ) || (
 +            (o1_min.x < o2_min.x && o1_min.y < o2_min.y && o1_min.z > o2_min.z) &&
 +            (o1_max.x < o2_max.x && o1_max.y < o2_max.y && o1_max.z > o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_min.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        ) || (
 +            (o1_min.x < o2_min.x && o1_min.y > o2_min.y && o1_min.z > o2_min.z) &&
 +            (o1_max.x < o2_max.x && o1_max.y > o2_max.y && o1_max.z > o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_min.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        ) || (
 +            (o1_min.x > o2_min.x && o1_min.y > o2_min.y && o1_min.z > o2_min.z) &&
 +            (o1_max.x > o2_max.x && o1_max.y > o2_max.y && o1_max.z > o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_min.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        ) || (
 +            (o1_min.x > o2_min.x && o1_min.y > o2_min.y && o1_min.z < o2_min.z) &&
 +            (o1_max.x > o2_max.x && o1_max.y > o2_max.y && o1_max.z < o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_min.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        ) || (
 +            (o1_min.x < o2_min.x && o1_min.y > o2_min.y && o1_min.z < o2_min.z) &&
 +            (o1_max.x < o2_max.x && o1_max.y > o2_max.y && o1_max.z < o2_max.z) &&
 +            (o1_min.x < o2_max.x && o1_min.y < o2_max.y && o1_min.z < o2_max.z) &&
 +            (o1_max.x > o2_min.x && o1_max.y > o2_min.y && o1_max.z > o2_min.z)
 +        )
 +    );   
 +}
 +</code>
 +
 +An example call of ''wasObjectIntersect'' is the following:
 +
 +<code lsl2>
 +default {
 +    state_entry() {
 +        key Oa = (key)"db20ac83-4333-754f-e0f4-fd104481f49d";
 +        key Ob = (key)"2c15e234-0ec9-f7e6-0be5-d507434352c7";
 +        if(wasObjectIntersect(Oa, Ob) == TRUE) {
 +            llOwnerSay("The objects intersect.");
 +            return;
 +        }
 +        llOwnerSay("The objects do not intersect.");
 +    }
 +}
 +</code>
 +
 +====== Determine the Bounding Box Points of an Object or Avatar ======
 +
 +Given an object ''O'' referenced by its key, the following function returns all the bounding-box points as a list.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// returns all points of the bounding box of object O
 +list wasBoundingPoints(key O) {
 +    list bb = llGetBoundingBox(O);
 +    vector min = llList2Vector(bb, 0);
 +    vector max = llList2Vector(bb, 1);
 +    vector pos = llList2Vector(llGetObjectDetails(O, [OBJECT_POS]), 0);
 +    
 +    return [
 +        <pos.x + min.x, pos.y + min.y, pos.z + min.z>,
 +        <pos.x + max.x, pos.y + max.y, pos.z + min.z>,
 +        <pos.x + min.x, pos.y + max.y, pos.z + min.z>,
 +        <pos.x + max.x, pos.y + min.y, pos.z + min.z>,
 +        <pos.x + min.x, pos.y + min.y, pos.z + max.z>,
 +        <pos.x + max.x, pos.y + max.y, pos.z + max.z>,
 +        <pos.x + min.x, pos.y + max.y, pos.z + max.z>,
 +        <pos.x + max.x, pos.y + min.y, pos.z + max.z>
 +    ];
 +}
 +</code>
 +
 +====== Determine the Bounding Box Sides of an Avatar or Object ======
 +
 +The function ''wasBoundingSides'', given a key of an object or avatar (''O''), returns pairs of points $P_{1}(x_{1}, y_{1});P_{2}(x_{2}, y_{2})$ where the points $P_{1}$ and $P_{2}$ represent a side $\overline{P_{1}P_{2}}$ of the bounding box. The function makes sure that the sides do not repeat, such that out of $P_{1}$, $P_{2}$, $P_{2}$, $P_{1}$ only $\overline{P_{1}P_{2}}$ is returned.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// returns all the sides of the bounding box
 +list wasBoundingSides(key O) {
 +
 +    list bb = llGetBoundingBox(O);
 +    vector min = llList2Vector(bb, 0);
 +    vector max = llList2Vector(bb, 1);
 +    vector pos = llList2Vector(llGetObjectDetails(O, [OBJECT_POS]), 0);
 +    
 +    vector p1 = <pos.x + min.x, pos.y + min.y, pos.z + min.z>;
 +    vector p2 = <pos.x + max.x, pos.y + max.y, pos.z + min.z>;
 +    vector p3 = <pos.x + min.x, pos.y + max.y, pos.z + min.z>;
 +    vector p4 = <pos.x + max.x, pos.y + min.y, pos.z + min.z>;
 +    vector p5 = <pos.x + min.x, pos.y + min.y, pos.z + max.z>;
 +    vector p6 = <pos.x + max.x, pos.y + max.y, pos.z + max.z>;
 +    vector p7 = <pos.x + min.x, pos.y + max.y, pos.z + max.z>;
 +    vector p8 = <pos.x + max.x, pos.y + min.y, pos.z + max.z>;
 +
 +    return [p1, p3, p1, p4, p1, p5, p2, p3, p2, p4, p2, p6, p3, p7, p4, p8, p5, p7, p5, p8, p6, p7, p6, p8];
 +}
 +</code>
 +
 +====== Determine if a Point falls within Rectangle Bounds ======
 +
 +Given a point $P$ and a rectangle bounded by ''x_min'', ''y_min'', ''x_max'' and ''y_max'': 
 +
 +<code>
 +x_min, y_max +-----+ x_max, y_max
 +                 |
 +                 |
 +x_min, y_min +-----+ x_max, y_min
 +</code>
 +
 +the function returns true if the point $P$ is inside the rectangle.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// returns true if the point p is within the rectangle bounds given by the 
 +// supplied x{min,max} and y{min,max} components of the rectangle bounds.
 +integer wasPointInRectangle(vector p, 
 +    float x_min, float y_min, float x_max, float y_max, ) {
 +    
 +    return (integer)(
 +        p.x < x_max && p.x > x_min && 
 +        p.y < y_max && p.y > y_min
 +    );
 +}
 +</code>
 +
 +====== Determine if a Segment Intersects a Rectangle ======
 +
 +Using [[fuss:lsl#determining_whether_two_segments_intersect|wasSegmentIntersect]] we can determine if a segment intersects a rectangle. Intuitively, this is done by checking whether the segment intersects each segment of the rectangle.
 +
 +The rectangle bounds ''x_min'', ''y_min'', ''x_max'' and ''y_max'' can be visualised as:
 +<code>
 +x_min, y_max +-----+ x_max, y_max
 +                 |
 +                 |
 +x_min, y_min +-----+ x_max, y_min
 +</code>
 +
 +A segment can intersect a rectangle in one or two points, however ''wasSegmentIntersectRectangle'' stops at the first intersection since the rest are redundant.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +// returns true if the segment pq intersects the rectangle given by the
 +// supplied x{min,max} and y{min,max} components of the rectangle bounds.
 +integer wasSegmentIntersectRectangle(vector p, vector q,
 +    float x_min, float y_min, float x_max, float y_max) {
 +    //
 +    // x_min, y_max +-----+ x_max, y_max
 +    //              |     |
 +    //              |     |
 +    // x_min, y_min +-----+ x_max, y_min
 +    //
 +    return (integer)(
 +        wasSegmentIntersect(<p.x, p.y, 0>, <q.x, q.y, 0>, 
 +            <x_min, y_min, 0>, <x_min, y_max, 0>) ||
 +        wasSegmentIntersect(<p.x, p.y, 0>, <q.x, q.y, 0>, 
 +            <x_min, y_min, 0>, <x_max, y_min, 0>) ||
 +        wasSegmentIntersect(<p.x, p.y, 0>, <q.x, q.y, 0>, 
 +            <x_max, y_max, 0>, <x_min, y_max, 0>) ||
 +        wasSegmentIntersect(<p.x, p.y, 0>, <q.x, q.y, 0>, 
 +            <x_max, y_max, 0>, <x_max, y_min, 0>)
 +    );
 +}
 +</code>
 +
 +An example call of the function is as follows:
 +<code lsl2>
 +default {
 +    state_entry() {
 +        vector A = <1, 1, 0>;
 +        vector B = <0, 0, 0>;
 +        if(wasSegmentIntersectRectangle(A, B, 2, 0, 0, 2) == TRUE) {
 +            llOwnerSay("Segment intersects rectangle.");
 +            return;
 +        }
 +        llOwnerSay("Segment does not intersect rectangle.");
 +    }
 +}
 +</code>
 +
 +====== Get Day of Week ======
 +
 +''wasDayOfWeek'' returns either "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" or "Sunday" from the input of the year, month and day.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasDayOfWeek(integer year, integer month, integer day) {
 +    return llList2String(
 +        [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", 
 +            "Saturday", "Sunday" ],
 +        (
 +            day
 +            + ((153 * (month + 12 * ((14 - month) / 12) - 3) + 2) / 5)
 +            + (365 * (year + 4800 - ((14 - month) / 12)))              
 +            + ((year + 4800 - ((14 - month) / 12)) / 4)
 +            - ((year + 4800 - ((14 - month) / 12)) / 100)
 +            + ((year + 4800 - ((14 - month) / 12)) / 400)
 +            - 32045
 +        ) % 7
 +    );
 +}
 +</code>
 +
 +For example, the following call:
 +<code lsl2>
 +        list stamp = llList2List(
 +            llParseString2List(
 +                llGetTimestamp(),
 +                ["-",":","T"],[""]
 +            ), 
 +        0, 2);
 +        llOwnerSay(
 +            wasDayOfWeek(
 +                llList2Integer(stamp, 0), 
 +                llList2Integer(stamp, 1), 
 +                llList2Integer(stamp, 2)
 +            )
 +        );
 +</code>
 +
 +will print out the current day of week.
 +
 +====== Floating-Point Modulo ======
 +
 +Since LSL's modulo operator ''%'' only works for integer types, the following function will allow perform modulo on ''a'' with ''p'':
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +float wasFmod(float a, float p) {
 +    if(p == 0) return (float)"nan";
 +    return a - ((integer)(a/p) * p);
 +}
 +</code>
 +
 +For example:
 +<code lsl2>
 +llOwnerSay((string)wasFmod(5.1, 3.0));
 +</code>
 +
 +will print out ''2.100000''.
 +
 +
 +====== Get Number of Days in a Month ======
 +
 +Given a month number and year number the ''wasGetMonthDays'' will return the number of days in the month.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasGetMonthDays(integer month, integer year) {
 +    if (month == 4 || month == 6 || month == 9 || month == 11) {
 +        return 30;
 +    }
 +    if(month == 2) {
 +        integer leap = (year % 4 == 0 && year % 100 != 0) || 
 +            (year % 400 == 0);
 +        if(leap == TRUE) {
 +            return 29;
 +        }
 +        return 28;
 +    }
 +    return 31;
 +}
 +</code>
 +
 +====== Get Number of Days in Year ======
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasGetYearDays(integer year) {
 +    integer leap = (year % 4 == 0 && year % 100 != 0) || 
 +            (year % 400 == 0);
 +    if(leap == TRUE) {
 +        return 366;
 +    }
 +    return 365;
 +}
 +</code>
 +
 +====== Unix Time to Stamp ======
 +
 +''wasUnixTimeToStamp'' takes as input a Unix timestamp (such as the one provided by the function ''llGetUnixTime'' and returns the time compatible with ''llGetTimestamp'' with the format:
 +
 +<code>
 +YYYY-MM-DDTHH:MM:SS.0Z
 +</code>
 +
 +The ''wasUnixTimeToStamp'' function does not compensate for trailing zeroes and relies on the ''wasGetMonthDays'' and ''wasGetYearDays'' functions.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasUnixTimeToStamp(integer unix) {
 +    integer year = 1970;
 +    integer dayno = unix / 86400;
 +    do {
 +        dayno -= wasGetYearDays(year);
 +        ++year;
 +    } while (dayno >= wasGetYearDays(year));
 +    integer month = 1;
 +    do {
 +        dayno -= wasGetMonthDays(month, year);
 +        ++month;
 +    } while (dayno >= wasGetMonthDays(month, year));
 +    return (string)year + "-" +
 +           (string)month + "-" +
 +           (string)(dayno + 1) + "T" +
 +           (string)((unix % 86400) / 3600) + ":" +
 +           (string)(((unix % 86400) % 3600) / 60) + ":" +
 +           (string)(unix % 60) + ".0Z";
 +}
 +</code>
 +
 +An example call is:
 +<code lsl2>
 +        llOwnerSay(
 +            wasUnixTimeToStamp(
 +                llGetUnixTime() - 
 +                ((integer) llGetGMTclock() - (integer) llGetWallclock())
 +            )
 +        );
 +</code>
 +
 +which displays the current date and time in ''PST or PDT''.
 +====== Get Bit Flags ======
 +
 +The function gets the flags in the ''mask'' provided a list of possible ''flags'' sorted in incremental order.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasGetBitFlags(integer mask, list flags) {
 +    list result = [];
 +    integer i = llGetListLength(flags)-1;
 +    do {
 +        if(((integer)llPow(2, i)) & mask)
 +            result += llList2String(flags, i);
 +    } while(--i>-1);
 +    return result;
 +}
 +</code>
 +
 +An example is the following:
 +<code lsl2>
 +        llOwnerSay(
 +            llDumpList2String(
 +                wasGetBitFlags(
 +                    75, 
 +                    [ "Build", "Explore", "Meet", "Group", "Buy", "Sell", "Be Hired", "Hire" ]
 +                ),
 +                ","
 +            )
 +        );
 +</code>
 +
 +The function would return:
 +<code>
 +Be Hired,Group,Explore,Build
 +</code>
 +
 +====== Set Bit Flags ======
 +
 +Returns a mask from the ''set'' flags out of a list of incrementally sorted list of ''flags''.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasSetBitFlags(list set, list flags) {
 +    if(llGetListLength(set) == 0) return 0;
 +    string flag = llList2String(set, 0);
 +    set = llDeleteSubList(set, 0, 0);
 +    return (integer)
 +        llPow(
 +            2, 
 +            llListFindList(flags, [flag])
 +        ) | wasSetBitFlags(set, flags);
 +}
 +</code>
 +
 +An example is issuing:
 +<code lsl2>
 +        llOwnerSay((string)
 +            wasSetBitFlags(
 +                ["Be Hired","Group","Explore","Build"],
 +                [ "Build", "Explore", "Meet", "Group", "Buy", "Sell", "Be Hired", "Hire" ]
 +            )
 +        );
 +</code>
 +
 +which will return ''75''.
 +
 +====== Replace all Sub-Strings or all Sub-List Elements ======
 +
 +Suppose that you have the string: ''abcdeeffgei'' and you want to replace a sequence such as ''ee'' by a different sequence such as ''z'' so that the outcome will be the string ''abcdzffgei''.
 +
 +The following solution is recursive:
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasListReplace(list l, string s, string r) {
 +    if(llGetListLength(l) == 0) return "";
 +    string q = llList2String(l, 0);
 +    l = llDeleteSubList(l, 0, 0);
 +    if(q == s) return r + wasListReplace(l, s, r);
 +    return q + wasListReplace(l, s, r);
 +}
 +
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasReplaceSubString(string i, string s, string r) {
 +    return wasListReplace(llParseString2List(i, [], [s]), s, r);
 +}
 +</code>
 +
 +an example call being:
 +<code lsl2>
 +llOwnerSay(wasReplaceSubString("abcdeeffgei", "ee", "z"));
 +</code>
 +
 +An iterative solution is the following:
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +string wasReplaceSubString(string i, string s, string r) {
 +    string result;
 +    list l = llParseString2List(i, [], [s]);
 +    do {
 +        string q = llList2String(l, 0);
 +        if(q == s) {
 +            result += r;
 +            jump continue;
 +        }
 +        result += q;
 +@continue;
 +        l = llDeleteSubList(l, 0, 0);
 +    } while(llGetListLength(l) != 0);
 +    return result;
 +}
 +</code>
 +
 +
 +====== Permute List and String Elements ======
 +
 +Given a string or list of elements these functions permute the string or list a certain number of times in both forward and reverse directions.
 +
 +===== Reverse =====
 +
 +The ''wasReversePermuteStringElements'' and ''wasReversePermuteListElements'' functions permute the elements of a string, respectively list, in reverse.
 +
 +Given a list of string containing the elements:
 +<code>
 +a b c
 +</code>
 +
 +and calling either function with the ''times'' parameter set to ''1'', we obtain the elements:
 +<code>
 +b c a
 +</code>
 +
 +==== String ====
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +string wasReversePermuteStringElements(string input, integer times) {
 +    if(times == 0) return input;
 +    return wasReversePermuteStringElements(
 +         llGetSubString(input, 1, -1) + llGetSubString(input, 0, 0), 
 +        --times
 +    );
 +}
 +</code>
 +
 +==== List ====
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +list wasReversePermuteListElements(list input, integer times) {
 +    if(times == 0) return input;
 +    return wasReversePermuteListElements(
 +         llList2List(input, 1, -1) + llList2String(input, 0), 
 +        --times
 +    );
 +}
 +</code>
 +
 +===== Forward =====
 +
 +The ''wasForwardPermuteStringElements'' and ''wasForwardPermuteListElements'' functions permute the elements of a string, respectively list, in reverse.
 +
 +Given a list of string containing the elements:
 +<code>
 +a b c
 +</code>
 +
 +and calling either function with the ''times'' parameter set to ''1'', we obtain the elements:
 +<code>
 +c a b
 +</code>
 +
 +==== String ====
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +string wasForwardPermuteStringElements(string input, integer times) {
 +    if(times == 0) return input;
 +    return wasForwardPermuteStringElements(
 +         llGetSubString(input, -1, -1) + llGetSubString(input, 0, -2), 
 +        --times
 +    );
 +}
 +</code>
 +
 +==== List ====
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +list wasForwardPermuteListElements(list input, integer times) {
 +    if(times == 0) return input;
 +    return wasForwardPermuteListElements(
 +         llList2String(input, -1) + llList2List(input, 0, -2), 
 +        --times
 +    );
 +}
 +</code>
 +
 +====== Permute a List To an Element ======
 +
 +Given an input list ''l'' containing the letters of the Latin alphabet:
 +<code lsl2>
 +list l = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" ]
 +</code>
 +
 +calling ''wasListPermuteToElement(l, "d")'' will return the list:
 +<code lsl2>
 +[ "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "a", "b", "c" ]
 +</code>
 +
 +The complexity of this algorithm is $O(1)$.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +list wasListPermuteToElement(list rot, string ring) {
 +    if(ring == llList2String(rot, 0)) return rot;
 +    integer i = llListFindList(rot, (list)ring);
 +    return ring + 
 +        llList2List(
 +            llDeleteSubList(
 +                rot, 
 +                i, 
 +                i
 +            ), 
 +            i, 
 +            -1
 +        )  + 
 +        llList2List(
 +            llDeleteSubList(
 +                rot, 
 +                i+1, 
 +                -1
 +            ), 
 +            0, 
 +            i-1
 +        );
 +}
 +</code>
 +
 +====== Cryptographic Functions ======
 +
 +The following is a list of cyphers implemented in LSL by Wizardry and Steamworks:
 +
 +{{indexmenu>fuss/lsl/cryptography/cyphers}}
 +
 +that can be used to encrypt and decrypt strings.
 +====== Case-Insensitive String Compare ======
 +
 +The ''wasCaseFoldStringCompare'' function takes as parameter two strings, performs an insensitive string comparison and returns ''TRUE'' in case the strings are equal or ''FALSE'' otherwise. The complexity is $O(1)$ in the best case and $O(n)$ in the worst case.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasCaseFoldStringCompare(string a, string b) {
 +    if(llStringLength(a) != llStringLength(b)) 
 +        return FALSE;
 +    if(a == "" || b == ""
 +        return TRUE;
 +    
 +    string A = llGetSubString(a, 0, 0);
 +    a = llDeleteSubString(a, 0, 0);
 +    string B = llGetSubString(b, 0, 0);
 +    b = llDeleteSubString(b, 0, 0);
 +
 +    if(llToUpper(A) == llToUpper(B))
 +         return wasCaseFoldStringCompare(a, b);
 +    
 +    return FALSE;
 +}
 +</code>
 +
 +An example call is the following:
 +<code lsl2>
 +wasCaseFoldStringCompare("Good day!", "GoOD dAy!");
 +</code>
 +which will return ''1'' since the strings are case-insensitive equal.
 +
 +====== Character Handling ======
 +
 +This set of functions operate on the first character of each string and are able to determine the type of character passed to these functions.
 +
 +{{indexmenu>secondlife/character_handling}}
 +
 +====== Fibonacci Number Generator ======
 +
 +The ''wasFibonacci'' function returns ''count''-sequential Fibonacci numbers starting from the initialization vector ''first'' and ''second'' (which can be either ''0'' and ''1'' or ''1'' and ''1'') as a list.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
 +///////////////////////////////////////////////////////////////////////////
 +list wasFibonacci(integer count, integer first, integer second) {
 +    if(count <= 0) return [];
 +    return first + wasFibonacci(--count, second, first+second);
 +}
 +</code>
 +
 +For example, to generate the first ''10'' Fibonacci numbers, issue:
 +<code lsl2>
 +llOwnerSay(llDumpList2String(wasFibonacci(10, 0, 1), ","));
 +</code>
 +
 +which will print out:
 +<code>
 +[01:47]  Object: 0,1,1,2,3,5,8,13,21,34
 +</code>
 +
 +====== Check if Avatar is in Sensor Range ======
 +
 +The ''wasIsAvatarInSensorRange'' function returns ''TRUE'' if and only if the avatar specified by the key ''avatar'' is in-range.
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +integer wasIsAvatarInSensorRange(key avatar) {
 +    return llListFindList(
 +        llGetAgentList(
 +            AGENT_LIST_REGION, 
 +            []
 +        ), 
 +        (list)((key)avatar)
 +    ) != -1 && 
 +        llVecDist(
 +            llGetPos(), 
 +            llList2Vector(
 +                llGetObjectDetails(
 +                    avatar, 
 +                    [OBJECT_POS]
 +                ), 
 +            0
 +        )
 +    ) <= 96;
 +}
 +</code>
 +
 +The judgement is made first on the premises that the specified ''avatar'' is in the current region and secondly that the ''avatar'' is  in a $[0, 96]m$ radius from the script.
 +
 +====== Smallest and Largest Segment ======
 +
 +Given a set of points, the following algorithms determine the smallest, respectively largest segment between the points.
 +
 +===== Smallest =====
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasSmallestSegment(list points) {
 +    list dists = [];
 +    list xi = [];
 +    list xj = [];
 +    integer i = llGetListLength(points) - 1;
 +    do {
 +        integer j = llGetListLength(points) - 1;
 +        do {        
 +            if(i == j) jump continue; 
 +            float d = llVecDist(
 +                llList2Vector(points, j), 
 +                llList2Vector(points, i)
 +            );
 +            dists += d;
 +            xi += i;
 +            xj += j;
 +@continue;
 +        } while(--j > -1);
 +    } while(--i > -1);
 +    
 +    i = llGetListLength(dists) - 1;
 +    float d = llList2Float(dists, i);
 +    --i;
 +    integer x = 0;
 +    integer y = 0;
 +    do {
 +        float t = llList2Float(dists, i);
 +        if(d > t) {
 +            x = llList2Integer(xi, i);
 +            y = llList2Integer(xj, i);
 +            d = t;
 +        }
 +    } while(--i > -1);
 +    
 +    return [llList2Vector(points, x), llList2Vector(points, y)];
 +}
 +</code>
 +===== Largest =====
 +
 +<code lsl2>
 +///////////////////////////////////////////////////////////////////////////
 +//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
 +///////////////////////////////////////////////////////////////////////////
 +list wasLargestSegment(list points) {
 +    list dists = [];
 +    list xi = [];
 +    list xj = [];
 +    integer i = llGetListLength(points) - 1;
 +    do {
 +        integer j = llGetListLength(points) - 1;
 +        do {        
 +            if(i == j) jump continue; 
 +            float d = llVecDist(
 +                llList2Vector(points, j), 
 +                llList2Vector(points, i)
 +            );
 +            dists += d;
 +            xi += i;
 +            xj += j;
 +@continue;
 +        } while(--j > -1);
 +    } while(--i > -1);
 +    
 +    i = llGetListLength(dists) - 1;
 +    float d = llList2Float(dists, i);
 +    --i;
 +    integer x = 0;
 +    integer y = 0;
 +    do {
 +        float t = llList2Float(dists, i);
 +        if(d < t) {
 +            x = llList2Integer(xi, i);
 +            y = llList2Integer(xj, i);
 +            d = t;
 +        }
 +    } while(--i > -1);
 +    
 +    return [llList2Vector(points, x), llList2Vector(points, y)];
 +}
 +</code>
 +
 +====== Inline Minimum of n-Values ======
 +
 +For two values (originally by Hermit Barber):
 +<code lsl2>
 +    return llAbs(x >= y) * y + 
 +            llAbs(x < y) * x;
 +</code>
 +
 +This relies on the principle that ''x >= y'' will expand to either ''TRUE'' or ''FALSE'' such that ''llAbs(FALSE)'' or ''llAbs(TRUE)'' will expand to ''0'' respectively ''1'' - the result is then multiplied by the smallest variable such that when $x \ge y$ then the outcome is the value of $1 * y$ and when $x < y$ the outcome is the value of $1 * x$.
 +
 +Inducing to three values:
 +<code lsl2>
 +    return llAbs((llAbs(x >= y) * y) > z) * z + 
 +            llAbs((llAbs(y > x) * x) >= z) * z + 
 +            llAbs((llAbs(z >= x) * x) > y) * y +
 +            llAbs((llAbs(x > z) * z) >= y) * y +
 +            llAbs((llAbs(y >= z) * z) > x) * x +
 +            llAbs((llAbs(z > y) * y) >= x) * x |
 +            // Base case where all values are equal x == y == z
 +            llAbs((llAbs(x == y) * x) == z) * x;
 +            
 +</code>
 +
 +The trick to the induction is to generate all possible permutations of $n$ variables and expand on the pattern whilst keeping the base-case where all values are equal. The base-case must be kept because the placement of $\ge$ ensures that when all three values are equal, the result is $0$ such that the logical $OR$ takes care to turn the result into the value of any variable (in this case, the value of $x$).
 +
 +====== Ternary Operator ======
 +
 +LSL does not have a ternary operator but a one-line branching decision can be expressed with:
 +<code lsl2>
 +string a = llList2String([ "dog", "ball" ], c == b);
 +</code>
 +where:
 +  * ''c'' and ''b'' are variables,
 +
 +such that ''a'' will contain the value "dog" when ''c'' is equal to ''b'' or the value ''ball'' otherwise.
 +
 +Remarkably, the expression preserves the same type constraints that a ternary operator would have: the type of ''a'' will be the same as either elements of the list (in this case converted via ''llList2String'') and the type of the variables ''c'' and ''b'' are free under the contraints of variables ''a'' and the values that ''a'' will hold (however, variables ''c'' and ''b'' must be of the same type).
 +
  

fuss/lsl.txt · Last modified: 2023/09/17 19:22 by office

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.