Table of Contents

Copyright

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

Empty List

Instead of:

if(!llGetListLength(l))) {
  llOwnerSay("not empty");
}

you can use:

default
{
    state_entry()
    {
        list l = [ 0 ];
        if(l) {
            llOwnerSay("not empty");
            return;
        }
        llOwnerSay("empty");
    }
}

For:

list l = [ 0 ];

Output:

Object: not empty

For:

list l = [];

Output:

Object: empty

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:

list a = [];
if(a) {
  // not empty
}
// empty

will work as explained earlier.

But:

list a = [];
if(a && TRUE) {
  // a not empty
}
// empty

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

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");

Usage

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");

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

integer o = -1;

Inline usage

    timer() {
        if(o = ~o) {
            /* Second timer() entry,fourth timer() entry,...,n+1th timer() entry. */
            return;
        }
        /* First timer() entry, third timer() entry,...,nth timer() entry. */
    }

ASCII Progress Bar

Globals

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

This function will return a string in this format:

symbols(0) + symbols(1) percent of length times + symbols(2) till 100 percent of length + symbols(3)

In other words, given a call:

progress(50, 10, ["[", "#", "o", "]"]);

it will return the string:

[#####ooooo]

This might come in handy together with llSetText and will allow you to display a progress bar, eg:

llSetText("Progress:\n" + wasProgress(50, 10, ["[", "#", "o", "]"]), <1,1,1>, 1.0);

Generate Group-Invite Links

Unless using bots, the only way to invite somebody to a group is to send them a link using the

secondlife:///

protocol which would make the viewer open the group profile window.

Inline Usage

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")

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

key nQuery = NULL_KEY;
integer nLine = 0;
list nList = [];
//pragma inline
string nName = "New Notecard";

Reading

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

Dataserver

    dataserver(key id, string data) {
        if(id != nQuery) return;
        if(data == EOF) return;
        if(data == "") jump continue;
        nList += data;
@continue;
        nQuery = llGetNotecardLine(nName, ++nLine);
    }

Extension: Return if key/name NOT in nList

        if(llListFindList(nList, (list)/* llDetectedKey(0), llDetectedName(0) */) == -1) return;

Side-By-Side Dual List Enumeration

Enumerates two lists side-by side, separating the elements by a separator.

Globals

list list_a = [ /* contents */ ];
list lib_b = [ /* contents */ ];

Inline usage

            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 ----------------");

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:

f -> 10
a -> 37
d -> 1
e -> 4
b -> 2
c -> 3
z -> 1

and we want to sort list_a lexicographically while preserving the mapping from letters to numbers above. The expected result should be:

a -> 37
b -> 2
c -> 3
d -> 1
e -> 4
f -> 10
z -> 1
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer stringComparer(string a, string b, integer ASCENDING) {
    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" 
    ];
 
    integer l = llListFindList(
        alph, 
        [ llToLower(a) ]
    );
 
    integer r = llListFindList(
        alph, 
        [ llToLower(b) ]
    );
 
    if(ASCENDING) {
        return l > r;
    }
 
    return l <= r;
}
 
///////////////////////////////////////////////////////////////////////////
//    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) == 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), " "));
    } 
}

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:

Object: Dual - Quicksort list contains in order: a 37 b 2 c 3 d 1 e 4 f 10 z 1

This is a symmetry-based variation of 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:

wasDualQuicksort([10, 5, 1, 20, 2, 4, 8], ["a", "b", "c", "d", "e", "f", "g"])

will return the list:

20,d,10,a,8,g,5,b,4,f,2,e,1,c
///////////////////////////////////////////////////////////////////////////
//    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);
}

RLV: Wearables & Attachment Lists as Strings

Local for less memory and less speed, global for more memory and more speed.

//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" ];

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 minify for fast deployment.

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

RLV: Rotate Avatar To Face Target

Variables

vector targetpos = < /* x */, /* y */, /* z */ >;

Inline usage

        vector pointTo = targetpos - llGetPos();
        float angleTo = llAtan2(pointTo.x, pointTo.y);
        llOwnerSay("@setrot:" + (string)angleTo + "=force");

Key2Number: Passed to Rezzed Objects

This should be used in both the rezzed object and the rezzer.

Global

integer Key2Number(key objKey) {
  return -(integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
}

This may be used anywhere in the rezzer script.

Rezzer: Inline usage

llRezObject(/* object name to rez */, /* ... */ ,Key2Number(/* Key to be passed to the rezzed object. */))

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

integer tgtID = 0;
key tgtKey = NULL_KEY;
integer nTgT = 0;

Used in the rezzed object itself.

Rezzed object: Inline usage

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

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

        llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, 60);

(No) Sensor Event Handler

    no_sensor() {
 
        /* this area will be hit 60 seconds
         * after the (no_)sensor handlers have
         * been installed with llSensorRepeat(). */
    }

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

      llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, TIME);

where TIME is a number, representing the number of seconds until no_sensor is triggered.

Clock - Triggered after TIME seconds

    // triggered after alarm * t seconds.
    no_sensor() {
        /* this area will be hit TIME seconds. */
    }

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:

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

Output:

Delayed by 10 seconds:

Object: Integer passed to me is: 10

Move to Mobile Point between Two Points

For teleport, we calculate a point that is at 10m between two points in space $A$ and $C$:

     d=10
 +----------+
 A          B                    C
 +----------+--------------------+

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:

float dist = llVectorDist(A, C); // calculated algebraically using generalized Pythagoras

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:

jumpDistance = 10/dist;

So, the final position is given by:

final_position = posA + jumpDistance * (posC-posA);

A function that computes $10m$ gates between two points is jumpGates:

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

Planar or Grid Distance

Distance is marked with red

The $x,y$ planar distance between the kitty and the book is given by:

float length_of_red_primitive=llVecDist(<kitty.x,kitty.y,0>,<book.x,book.y,0>);

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

The rotation required to be applied to the kitty object in order to face the book.

The rotation is given by $q\Delta$.

vector book_position = <83.889717, 92.310814, 500.5>;
vector kitty_position = <82.306671, 92.310814, 501.714783>;
 
default {
    state_entry() {
        rotation= 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() *);
    }
}

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:

  1. High angle.
  2. Low angle.
  3. Abort if object won't even lift off.

More explanations on the artillery page.

Globals

vector target = < /* x */, /* y */, /* z */ >;
float velocity = /* some velocity */;

Inline usage

        vector origin = llGetPos();
        dΔ=llVecDist(<target.x,target.y,0>,<origin.x,origin.y,0>);
        valSin = 9.81*/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);

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

integer Key2Number(key objKey) {
  return -(integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
}

Inline usage

llRezObject("object name", /* params */,Key2Number(some key));

Rezzed object event

default
{
    on_rez(integer param) {
        /* param contains the passed data */
    }
}

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:
llMessageLinked(/* link number the cacher script is in */, /* unused */, "kpre!", /* String of texture keys separated by commas. */);

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:
llMessageLinked(sender, 0, "kok", "");

where sender is the primitive that sent the caching request in the first place.

Full script:

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

Time Dilation Normed to 10

A short snippet that sets the primitive's text to the time dilation using the formula:

dilation_norm_10 = (1.0 - dilation) * 10

Full script:

default
{
    state_entry() {
        llSetTimerEvent(1);
    }
 
    timer() {
        llSetText("Time Dilation: " + (string)((integer)((1.0-llGetRegionTimeDilation())*10.0)), <1,1,1>, 1.0);
    }
}

Fast Integer Exponentiation

Works for integers, ex: place a call with

fsPow(2, 2) 

for the function to return the value of 2^2.

integer fsPow(integer base, integer exponent) {
    integer result = 1;
    do {
        if(exponent & 1) result *= base;
        exponent = exponent >> 1;
        base *= base;
    } while(exponent);
    return result;
}

Xorshift

Fast random number generator, invented by George Marsaglia, implemented in LSL by us and passes all the diehard tests.

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

TRNG

True random-number generator, based on FPS fluctuations of region frame-rates.

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

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:

A -> 60%
B -> 10%
C -> 10%
D -> 20%

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.

    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.

Compute Capping System Delays

A calculator to compute the random amount of time needed (in seconds) to wait between executing some capped action.

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

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:

Marry had a
little lamb,
Its fleece
was white
as snow; And 
everywhere
that Mary
went, The
lamb was sure
to go.

Global

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

Rename Primitives in a Linked Set

Uses the new llTextBox, nuked with minify for fast deploy.

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

String Reverse

Recursive string reversal; sweet solution from code monkeys.

Global

string wasStringReverse(string str) {
    if(llStringLength(str)<=1) return str;
    return wasStringReverse(llGetSubString(str,1,llStringLength(str))) + llGetSubString(str,0,0);
}

Example

llOwnerSay(wasStringReverse("שלום, עולם!"));

Output

Object: !םלוע ,םולש

List Reverse

Same as above but using lists instead of strings.

Global

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

Example

list CLOTHES = [ "gloves","jacket","pants","shirt","shoes","skirt","socks","underpants","undershirt","skin","eyes","hair","shape" ];
llOwnerSay(llDumpList2String(wasListReverse(CLOTHES), " "));

Output

shape hair eyes skin undershirt underpants socks skirt shoes shirt pants jacket gloves

XML Stream Parser

This is a StAX stream parser which is able to read simplexml.

Get Node Value

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

Set Node Value

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

wasBioLuminescence

Can be called every hour to make lagoons glow and simulate oceans or seas.

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

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:

var_c = var_a << 16 | var_b

Unpack:

var_a = var_c >> 16;
var_b = var_c & 0xFFFF;

Permute Sort

Permute sort, uses a reference string to sort an input string.

Example reference:

rraaats

example input:

taaas

example output:

aaats
/////////////////////////////////////////////
// [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;
 
}

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:

2 3 8 2 4 6

and value parameter equal to 1.

Result:

1 2 7 1 3 5

Returns -1 on empty list. integer type can be replaced with float for subtracting float values.

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

Add-up all List Elements

The following recursive function can be used instead of llListStatistics.

Example input:

2 3 8 2 4 6

Example output:

25

Returns 0 on empty list.

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

Remove Multiple Elements From List

Input:

wasSubtractSubList(["a", "b", "c", "d"], ["c", "d"])

Output:

a b

Input:

wasSubtractSubList(["a", "b", "c", "d"], ["b", "d"])

Output:

a c

Input:

wasSubtractSubList(["a", "b", "c", "d"], ["b", "a"])

Output:

c d
///////////////////////////////////////////////////////////////////////////
//    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;
}

Generate UUID v4

Input:

        key gen = wasUUIDgen(234234);
        if(gen) {
            llOwnerSay("Valid key: " + (string)gen);
        }

Output:

Valid key: e5259984-f745-4398-a5f7-2317cc724d9a
///////////////////////////////////////////////////////////////////////////
//    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;
}

Levenshtein Distance

The Levenshtein distance is equal to the number of single-character edits required to change one word into the other.

Input:

wasLevenshteinDistance("kitten", "sitting")

Return:

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

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:

wasMinimum([10, 1, 9, 20, 8])

Return:

1

Minimal Value

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

Maximal Value

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

Reasoning

The reasoning is based on using lists as queues:

  1. [Base Case] If the queue contains just one element, return that element.
  2. If the second element in the queue is smaller than the first element in the queue:
    1. Delete the first element in the queue and replace it with the second element in the queue.
  3. Delete the second element in the queue.
  4. return queue of (1).

The only difference between wasMinimum and wasMaximum is that the sign is inverted in the comparison:

    if(j < i) {

for finding the minimal value, and

    if(j > i) {

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:

wasListUnique([1, 1, 2, 1, 3, 1, 5, 2, 2, 2, 7, 8], FALSE)

Return:

[1, 2, 3, 5, 7, 8]
///////////////////////////////////////////////////////////////////////////
//    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);
}

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:

wasDeleteSubListMatch(["aabbcc", "dddd", "eeffgg", "rrraatt"], "e")

Return:

[ "aabbcc", "dddd", "rrraatt"]
///////////////////////////////////////////////////////////////////////////
//    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);
}

Number to Roman Numeral

Input:

23

Output:

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

Pretty Numbers

Call:

wasPrettyNumbers(15, [ "⓪", "⓵", "⓶", "⓷", "⓸", "⓹", "⓺", "⓻", "⓼", "⓽" ]);

Return:

⓵⓹
///////////////////////////////////////////////////////////////////////////
//    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;
}

Permute a Set

Returns a list containing all the permutations of a set.

A call such as:

wasPermuteSet(["a", "b"], 0, 2)

where 0 represents the start index and 2 represents the length of the set, will return the list containing the elements:

a b b a

wasPermuteSet is a recursive function that uses wasListSwap as an auxiliary function:

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

Calculate Factorial

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
// Calculates n!
integer wasFactorial(integer n) {
    if(n == 1) return 1;
    return wasFactorial(--n) * n;
}

Insertion Sort

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

Remove Bling and Particle Effects

Copy and paste into any script inside a primitive to remove the bling and any other particle effects.

default{state_entry(){llParticleSystem([]);}}

Remove Texture Animation

Copy and paste into any script inside a primitive to stop all texture animations.

default{state_entry(){llSetTextureAnim(0,-1,0,0,0.0,0.0,1.0);}}

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 the zombies game. For a demonstration see the bistable color primitive page.

For example, a call such as:

wasPercentToGradient(50, "rg");

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>.

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

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.

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

Shuffle Lists

Some remarks:

  • if we were to calculate the Levenshtein distance of a 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 wasShuffleList function, does allow the selected element to be the current element so that its position is never exchanged.

Knuth Shuffle

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

Shuffle List Randomly

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

Binary Search

Binary search expects the data list to be sorted and returns the element if found or -1 otherwise.

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

For a complete binary-tree and binary search tree (BST) implementation in LSL see the binary trees section.

Notes

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:

jot - 1 1000

The goal was to find the element 53, which shows how binary search outperforms llListFindList by far:

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

On subsequent runs, the same delta was observed between 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:

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

Day Of Week

Adapted from Doomsday.

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

Modular Exponentiation

With O(n) Complexity

Computes $a^{b} \mod{m}$ with $O(b)$ complexity.

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

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.
$$

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

Check if Number is Prime

Uses 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:

wasIsPrime(7, 1);

returns 1.

Example:

wasIsPrime(24, 1);

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})$.

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

Date to Unix Timestamp

From the Linux kernel, converts YYYY-MM-DD HH:MM:SS to Unix timestamp.

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

Activity Spinner

The following function will rotate the spinner once and can be used separately in scripts.

///////////////////////////////////////////////////////////////////////////
//    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 + "]";
}

To make the spinner move one step forward, call:

spin()

which returns [↗] where ↗ is the next position of the arrow.

You can find some collected sets of spinners on the 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.

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

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 dice page.

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

If rotation is desired on one single axis only, the following modification will do that:

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

Count Consecutive Characters in Strings

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

String to Vector or Rotation

vector v = (vector)"<1, 2, 3>";
rotation r = (rotation)"<1, 2, 3, -1>";

List to Vector or Rotation

list a = [ "3", "2", "1" ];
vector v = (vector)("<" + llList2CSV(a) + ">");
 
a = [ "3", "2", "1", "-1" ];
rotation r = (rotation)("<" + llList2CSV(a) + ">");

Mixed-Type List to Type

The following function converts a list of mixed types to a list of type T.

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

For example, suppose you have a compound list:

list a = [ 0, NULL_KEY, 3.546 ];

and you want to convert all the values to a float type. You would then do:

llOwnerSay(
            llDumpList2String(
                wasCompoundToList(TYPE_FLOAT, a),
            ",")
        );

which would say to the owner:

0.000000,0.000000,3.546000

An example of the usefulness of the function can be found on the 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.

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

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:

vector p = <23.499, 134, 39.048245>;
list intList = wasCompoundToType(TYPE_INTEGER, [p]);

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:

vector p = <23.499, 134, 39.048245>;
list floatList = wasCompoundToType(TYPE_FLOAT, [p]);

the list floatList will contain a list of floats from the components of vector p (see below).

When a string type is requested:

vector p = <23.499, 134, 39.048245>;
list truncatedStringList = wasCompoundToType(TYPE_STRING, [p]);

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 jump, where string concatenation is supposed to build an SLURL out of a positional vector:

string slurl = llGetRegionName() + "/" + 
  llDumpList2String(
    llList2ListStrided(llParseString2List((string)llGetPos(), ["<", " ", ",", ".", ">"], []), 0, -1, 2)
  "/")

and so is this:

vector p = llGetPos();
string slurl = llGetRegionName() + "/" + 
  (string)((integer)p.x) + "/" + 
  (string)((integer)p.y) + "/" + 
  (string)((integer)p.z);

However, instead of writing a really large line and to avoid allocating a new variable p, we can instead write:

string slurl = llGetRegionName() + "/" + 
  llDumpList2String(
    wasCompoundToType(TYPE_INTEGER, [llGetPos()]), 
  "/")

Merge Sort

Example:

wasMergeSort([20, 5, 1, 11, 12, 32, 8, 3, 4, 7])

Returns the list:

1 3 4 5 7 8 11 12 20 32
///////////////////////////////////////////////////////////////////////////
//    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)
        )
    );
}

Private Channel Key-Hash

Extremely useful, taken from the llDialog wiki page:

Inline Usage

integer comChannel = ((integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;

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:

84c08dd4-7009-6b21-4645-d0d3ed5265df

We know that integer types start from 0x80000000 which represents −2,147,483,648. So we extract 7 characters off the UUID:

84c08dd

Now, we know that we want a negative number, so we concatenate 0x8 to 84c08dd and obtain:

0x884c08dd

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:

integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));

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:

list input = ["a", 1, 0.45, "", 43];

when called with:

wasListCount(input, [""]);

the function will return 4.

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

Merge Lists

The following function will merge two lists l and m, on the marker merge, given the positions of the merger list m.

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

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:

list a = [ 1, "", 2 ];
list b = [ "", "b" ]; 

when calling the function with:

wasListMerge(a, b, "");

it will return the list:

[ 1, b, 2 ];

Counter-Example

Suppose we have the following lists:

list a = [ 1, "", 2 ];
list b = [ "a", "b" ]; 

when calling the function with:

wasListMerge(a, b, "");

it will return the list:

[ "a", "b", 1, 2 ];

Practical Example

Suppose that we have a menu list that is passed to llDialog, such as:

list menu = [ "Blue", "Red", "Green", "White", "Black" ];

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:

wasListMerge(menu, ["<= Back", "", "Next =>"], "");

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 wasListCountExclude and wasListMerge we can build an endless menu with customizable buttons. For an in-depth explanation on the usage of this function, please see the pretty menu system.

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

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.

Key-Value Data

The following functions allow processing of key-value data. An overview is provided on the key-value data page.

Empty String or Blank

Similar to isspace in C, but returns TRUE for the empty strings as well.

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

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.

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

Relative Positions

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:

vector A = <xa, ya, za>

We also know the rotation of object A:

rotation RA = <xar, yar, zar, sar>;

Next, let's say we the object B at:

vector B = <xb, yb, zb>

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:

vector T = -(A-B)/RA;

which we can store.

Later, we can set the position of object B to:

vector NEW_B = A + T*RA.

This is used in the 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

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:

rotation RA = <a.x, a.y, a.z, a.s>

and suppose we have the final rotation:

rotation RB = <b.x, b.y, b.z, b.s>

we first calculate the transition rotation:

rotation T = <RA.x,RA.y,RA.z,-RA.s> * RB;

and we set the new object's rotation to:

llSetRot(RA * T);

This was used for the 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 lsl) 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.

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

Binary Trees and Binary Search 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.

if (llListFindList(aList, (list)((string)llDetectedKey(0))) == -1) return;

Event Handler Order

Event Handler State Machine
Attach Object (from inventory) on_rezattach (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_rezattachstate_entry
Logout with item attached $\phi$ (script retains last state)
State-change state_exitstate_entry
Rez in-world on_rezstate_entry

LID™

As explained by the LID™ page, the following function can be used to detect script resets or :

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

has to be called in the attach event handler:

    attach(key id) {
        LID(id);
    }

RGB to Color

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasRGBToColor(vector rgb) {
    return <rgb.x / 255, rgb.y / 255, rgb.z / 255>;
}

Color to RGB

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasColorToRGB(vector color) {
    return <color.x * 255, color.y * 255, color.z * 255>;
}

Center String for Dialog Message

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

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 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).

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

Elipse Circumference

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

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.

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

The function returns TRUE if p is inside the polygon and FALSE otherwise.

Path Simplification Algorithm

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.

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

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:

llOwnerSay("Result: " + llDumpList2String(wasPartitionString("abc", 2), "|"));

will return a list:

[ "ab", "c"]

The function operates by divide and conquer and takes care to not return the empty string in the borderline case $length(data) \leq n$.

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

Repeat a String

A call such as:

wasRepeatString("✪✪", " ", 4)

will return the string:

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

Rotate a Linked Primitive around Other Linked Primitives

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:

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

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:

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

The wasRotateAroundLinkNumber first computes the 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:

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:

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

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:

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

An example input is:

list a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];

and its corresponding output:

5,6,4,7,3,8,2,9,1

Imploding a list means to take the extremes of the list and to group them at the front of the list:

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

An example input is:

list a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];

and its corresponding output:

1,9,2,8,3,7,4,6,5

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:

name=CP-3&result=good&test=ok

and returns a string with the escaped values:

name=CP%2D3&result=good&test=ok
///////////////////////////////////////////////////////////////////////////
//    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, "&");
}

The function can be used to send form data to a server-side script, for example:

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

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:

$name = urldecode($_POST['name']); // CP-3
$result = urldecode($_POST['result']); // good
$test = urldecode($_POST['test']); // ok

Determining Whether Two Segments Intersect

Given four points (A, B, C and D) defined in parametric form, such as:

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

In order to determine whether the segments $AB$ and $CD$ intersect, one would call the function as:

if(wasSegmentIntersect(A, B, C, D) == TRUE) {
    llOwnerSay("Segments AB and CD intersect.");
    return;
}
llOwnerSay("Segments AB and CD do not intersect.");

wasSegmentIntersect is defined as:

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

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.

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

An example call of wasObjectIntersect is the following:

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.");
    }
}

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.

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

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.

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

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:

x_min, y_max +-----+ x_max, y_max
             |     |
             |     |
x_min, y_min +-----+ x_max, y_min

the function returns true if the point $P$ is inside the rectangle.

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

Determine if a Segment Intersects a Rectangle

Using 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:

x_min, y_max +-----+ x_max, y_max
             |     |
             |     |
x_min, y_min +-----+ x_max, y_min

A segment can intersect a rectangle in one or two points, however wasSegmentIntersectRectangle stops at the first intersection since the rest are redundant.

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

An example call of the function is as follows:

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.");
    }
}

Get Day of Week

wasDayOfWeek returns either "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" or "Sunday" from the input of the year, month and day.

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

For example, the following call:

        list stamp = llList2List(
            llParseString2List(
                llGetTimestamp(),
                ["-",":","T"],[""]
            ), 
        0, 2);
        llOwnerSay(
            wasDayOfWeek(
                llList2Integer(stamp, 0), 
                llList2Integer(stamp, 1), 
                llList2Integer(stamp, 2)
            )
        );

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:

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

For example:

llOwnerSay((string)wasFmod(5.1, 3.0));

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.

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

Get Number of Days in Year

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

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:

YYYY-MM-DDTHH:MM:SS.0Z

The wasUnixTimeToStamp function does not compensate for trailing zeroes and relies on the wasGetMonthDays and wasGetYearDays functions.

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

An example call is:

        llOwnerSay(
            wasUnixTimeToStamp(
                llGetUnixTime() - 
                ((integer) llGetGMTclock() - (integer) llGetWallclock())
            )
        );

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.

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

An example is the following:

        llOwnerSay(
            llDumpList2String(
                wasGetBitFlags(
                    75, 
                    [ "Build", "Explore", "Meet", "Group", "Buy", "Sell", "Be Hired", "Hire" ]
                ),
                ","
            )
        );

The function would return:

Be Hired,Group,Explore,Build

Set Bit Flags

Returns a mask from the set flags out of a list of incrementally sorted list of flags.

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

An example is issuing:

        llOwnerSay((string)
            wasSetBitFlags(
                ["Be Hired","Group","Explore","Build"],
                [ "Build", "Explore", "Meet", "Group", "Buy", "Sell", "Be Hired", "Hire" ]
            )
        );

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:

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

an example call being:

llOwnerSay(wasReplaceSubString("abcdeeffgei", "ee", "z"));

An iterative solution is the following:

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

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:

a b c

and calling either function with the times parameter set to 1, we obtain the elements:

b c a

String

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

List

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

Forward

The wasForwardPermuteStringElements and wasForwardPermuteListElements functions permute the elements of a string, respectively list, in reverse.

Given a list of string containing the elements:

a b c

and calling either function with the times parameter set to 1, we obtain the elements:

c a b

String

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

List

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

Permute a List To an Element

Given an input list l containing the letters of the Latin alphabet:

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" ]

calling wasListPermuteToElement(l, "d") will return the list:

[ "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" ]

The complexity of this algorithm is $O(1)$.

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

Cryptographic Functions

The following is a list of cyphers implemented in LSL by Wizardry and Steamworks:

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.

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

An example call is the following:

wasCaseFoldStringCompare("Good day!", "GoOD dAy!");

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.

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.

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

For example, to generate the first 10 Fibonacci numbers, issue:

llOwnerSay(llDumpList2String(wasFibonacci(10, 0, 1), ","));

which will print out:

[01:47]  Object: 0,1,1,2,3,5,8,13,21,34

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.

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

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

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

Largest

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

Inline Minimum of n-Values

For two values (originally by Hermit Barber):

    return llAbs(x >= y) * y + 
            llAbs(x < y) * x;

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:

    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;
 

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:

string a = llList2String([ "dog", "ball" ], c == b);

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).

Clamping Vector Components

wasVectorClamp takes a vector v and ensures that all vector components will fall within the closed interval $[min, max]$.

///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2022 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasVectorClamp(vector v, float min, float max) {
    return
        <
            llListStatistics(
                LIST_STAT_MAX,
                [
                    llListStatistics(
                        LIST_STAT_MIN,
                        [
                            v.x,
                            max
                        ]
                    ),
                    min
                ]
            ),
            llListStatistics(
                LIST_STAT_MAX,
                [
                    llListStatistics(
                        LIST_STAT_MIN,
                        [
                            v.y,
                            max
                        ]
                    ),
                    min
                ]
            ),
            llListStatistics(
                LIST_STAT_MAX,
                [
                    llListStatistics(
                        LIST_STAT_MIN,
                        [
                            v.z,
                            max
                        ]
                    ),
                    min
                ]
            )
        >;
}

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.