Wizardry and Steamworks/LSL

From Second Life Wiki
Jump to: navigation, search
KBnote.png Note: Due to licensing issues, all contributions to this wiki have stopped and the articles that we posted are just being maintained. This is one of the projects that has gone further and for updates you are cordially invited to the project page on our wiki.

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 to determining whether a vector is the ZERO_VECTOR.

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

Universal NOR using Vector Inputs

Universal NOR


Optimised by Strife Onizuka:

vector I =  <FALSE,FALSE,FALSE>;
llOwnerSay("Inputs: " + (string)I);
integer Q = !((integer)I.x || (integer)I.y || (integer)I.z);
//or even
integer Q = !(integer)(I * I);//this is a cheat and computationally more expensive but oh so elegant.
llOwnerSay("NOR Q: " + (string)Q);

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

string progress(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" + progress(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")

Private Channel Key-Hash

Extremely useful, taken from llDialog.

Inline Usage

integer comChannel = ((integer)("0x"+llGetSubString((string) 
    /* llGetOwner(), llGetCreator(), llDetectedKey(0), id */,-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;

Endless Menu with Back and Next

This generates an endless menu with Back and Next buttons by arranging a list dynamically whenever the Back and Next buttons are pressed. It has been used in several scripts with some variations.

Globals

list menu_items = [];
integer mitra = 0;
list cList = [];

Global

list mFwd() {
    if(mitra+1>llGetListLength(menu_items) || llGetListLength(menu_items) == 10) return cList;
    cList =  llListInsertList(llListInsertList(llList2List(menu_items, ++mitra, (mitra+ =9)), 
      ["<=  Back"], 0), ["Next  =>"], 2);
    return cList;
} 
 
list mBwd() {
    if(mitra-19<0) return cList;
    cList =  llListInsertList(llListInsertList(llList2List(menu_items, (mitra- =19), (mitra+=9)), 
      ["<=  Back"], 0), ["Next  =>"], 2);
    return cList;
}

Inline Usage

            integer itra;
            for(itra= 0, mitra =0, menu_items=[]; itra< llGetListLength(/* list */); ++itra) {
                menu_items += llList2String(/* list */, itra);
            }
            cList =  llListInsertList(llListInsertList(llList2List(menu_items, mitra, (mitra+ =9)), 
              ["<=  Back"], 0), ["Next  =>"], 2);
            llDialog(/* Key to prompt */, /* Dialog Text */, cList, /* channel */);

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

    integer itra = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
    do {
        if(llGetInventoryName(INVENTORY_NOTECARD, itra) == nName)
            jump found_notecard;
    } while(--itra>=0);
    llOwnerSay("Failed to find notecard.");
    return;
@found_notecard;
    nQuery = llGetNotecardLine(nName, nLine);

Dataserver

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

Extension: Return if key/name NOT in nList

        if(!~llListFindList(nList, (list)/* llDetectedKey(0), llDetectedName(0) */)) 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
list list_a = ["f", "a", "d", "e", "b", "c", "z"];
list list_b = [ 10, 37,   1,   4,   2,   3 ,  1 ];
 
integer stringComparer(list a, list b) {
 
    list alph = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", 
                      "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" ];
 
    integer i;
 
    for(i=0; i<llStringLength(llList2String(a, 0)) && i<llStringLength(llList2String(b, 0)); i++) {
        if(llListFindList(alph, (list)llGetSubString(llList2String(a, 0), i, i)) < 
            llListFindList(alph, (list)llGetSubString(llList2String(b, 0), i, i))) {
            return TRUE;
        }
        if(llListFindList(alph, (list)llGetSubString(llList2String(a, 0), i, i)) > 
            llListFindList(alph, (list)llGetSubString(llList2String(b, 0), i, i))) {
            return FALSE;
        }
 
    }
    return TRUE;
}
 
list dualQuicksort(list a, list b) {
 
    if(llGetListLength(a) <= 1) return a+b;
 
    list pivot_a = llList2List(a, llGetListLength(a)/2, llGetListLength(a)/2);
    list pivot_b = llList2List(b, llGetListLength(b)/2, llGetListLength(b)/2);
 
    a = llDeleteSubList(a, llGetListLength(a)/2, llGetListLength(a)/2);
    b = llDeleteSubList(b, llGetListLength(b)/2, llGetListLength(b)/2);
 
    list less = [];
    list less_b = [];
    list more = [];
    list more_b = [];
 
    integer i;
    for(i=0; i<llGetListLength(a)*2; i++) {
        if(stringComparer(llList2List(a, i, i), pivot_a) == TRUE) 
        {
            less += llList2List(a, i, i);
            less_b += llList2List(b, i, i);
        }
        else
        {
            more += llList2List(a, i, i);
            more_b += llList2List(b, i, i);
        }
    }
    return  dualQuicksort(less, less_b) + (list)pivot_a + (list)pivot_b + dualQuicksort(more, more_b);
}
 
default
{
    state_entry()
    {
        llOwnerSay("Dual - Quicksort list contains in order: " + 
          llDumpList2String(quicksort(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 followed by 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 x 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.

list dualQuicksort(list a, list b) {
 
    if(llGetListLength(a) <= 1) return a+b;
 
    float pivot_a = llList2Float(a, llGetListLength(a)/2);
    string pivot_b = llList2String(b, llGetListLength(b)/2);
 
    a = llDeleteSubList(a, llGetListLength(a)/2, llGetListLength(a)/2);
    b = llDeleteSubList(b, llGetListLength(b)/2, llGetListLength(b)/2);
 
    list less = [];
    list less_b = [];
    list more = [];
    list more_b = [];
 
    integer i = 0;
    do {
        if(llList2Float(a, i) > pivot_a) 
        {
            less += llList2List(a, i, i);
            less_b += llList2List(b, i, i);
        }
        else
        {
            more += llList2List(a, i, i);
            more_b += llList2List(b, i, i);
        }
    } while(++i<llGetListLength(a)*2);
 
    return dualQuicksort(less, less_b) + [ pivot_a ] + [ pivot_b ] + dualQuicksort(more, more_b);
}

RLV: Wearables & Attachment Lists as Strings

A pain to type.

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 llAbs(((integer)("0x"+llGetSubString((string)objKey,-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF);
}

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) {
        integer itra;
        for(itra=0; itra<num_detected; ++itra) {
            tgtKey = llDetectedKey(itra);
            if(Key2Number(tgtKey) == tgtID) { 
                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);
                return;
            }
        }
    }

Using llSensorRepeat as a Timer

Can be used as a second timer event. An example thereof, can be found on the Looping Two Sounds page.

Globals;

integer flag = 0;

Starting the sensor-timer

        llSensorRepeat("", NULL_KEY, flag=1, 0.1, 0.1, 60);

(No) Sensor

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

llSensorRepeat as alarm()

This is the equivalent of an alarm in LSL using llSensorRepeat.

That is, no_sensor will be triggered after

t * alarm

seconds.

Globals

integer alarm = 0;

Used to install the alarm:

Installing (no_)sensor handlers

// alarm = 1 should be here for OpenSIM
llSensorRepeat("", NULL_KEY, alarm=  /* set this to an integer, move inline up if OpenSIM. */, 0.1, 0.1, /* t =10 */);

Re-schedule alarm

      /* no_sensor will trigger after alarm * t seconds.
       * if t = 10 was used in llSensorRepeat, then setting
       * this to 5, will make no_sensor trigger after 50 seconds.
       * If you wish the alarm to be raised, after the
       * next t seconds cycle, set this to 1.
       */
      alarm = 5;

Clock - Decays alarm by 1 every t seconds

    // triggered after alarm * t seconds.
    no_sensor() {
        if(flag) {
            --flag;
            return;
        }
 
        /* this area will be hit t * alarm seconds
         * after the (no_)sensor handlers have
         * been installed with llSensorRepeat(). */
    }

Data-Passing over States 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(); // <-- -- MARK --
        state catch;
    }
}
 
state catch
{
    timer() {
        llSetTimerEvent(0);
        llSay(0, "Integer passed to me is: " + (string)((integer)llGetAndResetTime()));
    }
}

Output:

Delayed by 10 seconds:

Object: Integer passed to me is: 10

Move to Arbitrary 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 AC. 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 generalised Pythagoras
<source lang="lsl2">
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:
<source lang="lsl2">
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:

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

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 at the Artillery article.

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 object 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 llAbs(((integer)("0x"+llGetSubString((string)objKey,-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF);
}

Inline usage

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

Rezzed object event

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

Round-Robin Link-Message Scheduler

Given a circular linked-list with 3 nodes, we can build a round-robin scheduler that could serve for some cyclic synchronisation source.

With three nodes, the list can be represented as:

[Robin_1] -> [Robin_2] -> [Robin_3]
      ^                    |
      |                    |
      +---------------------

Robin_1

// Robin_1 -> Robin_2 -> Robin_3 -> Robin_1 -> ...
default
{
    touch_start(integer num)
    {
        // Begin.
        llMessageLinked(LINK_THIS, 1, llGetScriptName() + ": sync", "");
    }
 
    link_message(integer sender_num, integer num, string str, key id)
    {
        if(num!=1) return;
        llOwnerSay(str);
        llMessageLinked(LINK_THIS, 2, llGetScriptName() + ": sync", "");
    }
}

Robin_2

// Robin_1 -> Robin_2 -> Robin_3 -> Robin_1 -> ...
default
{
    link_message(integer sender_num, integer num, string str, key id)
    {
        if(num!=2) return;
        llOwnerSay(str);
        llMessageLinked(LINK_THIS, 3, llGetScriptName() + ": sync", "");
    }
}

Robin_3

// Robin_1 -> Robin_2 -> Robin_3 -> Robin_1 -> ...
default
{
    link_message(integer sender_num, integer num, string str, key id)
    {
        if(num!=3) return;
        llOwnerSay(str);
        llMessageLinked(LINK_THIS, 1, llGetScriptName() + ": sync", "");
    }
}

this implementation schedules three threads in a round-robin fashion based on three scripts in the same primitive. Other implementations may pass the pallet using the link number directly instead of broadcasting.

Connections to other domains:

  • Simulation of harmonic functions (based on oscillations between threads exchanging pallets, general oscillations).
  • Multivibrators (multiphase) (when one thread has the pallet, the other does not).
  • Domino logic (each script represents a gate, a number is passed through the gates and incremented each time thereby achieving the cascading effect).

... you get the picture.

Data Persistency Using Textures

This would allow you to store a string message by forcing a texture to an UUID which would resist log-in and log-out. Created initially a very long time ago for somebody and used much later on with the elven ears created by Kira Komarov. The possible number of messages to store is equal to the number of faces.

  • To store a message:
llMessageLinked(/* link number the memory module script is in */, /* face number to store on */, /* message to store */, "@push");
  • To retrieve a message:
llMessageLinked(/* link number the memory module script is in */, /* face number to retrieve the message from */, "", "@pull");

Memory Module Script

Published at Memory Module.

Full script:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
//////////////////////////////////////////////////////////
// Requires: link message, integer set to face number to 
// store on, string set to message to store, key set to 
// "@push" to store or key set to "@pull" to fetch stored 
// information on face.
// Provides: link message if retrieving, integer set to 
// face on which message is stored, string set to stored 
// message on face and key set to "@pull".
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
// About: A script to store data permanently over script reset.
//////////////////////////////////////////////////////////
list cSelect(integer hex) {
	if(hex) return ["20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f","30","31","32","33","34","35","36","37","38","39","3a","3b",
                  "3c","3d","3e","3f","40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f","50","51","52","53","54","55","56","57","58",
                  "59","5a","5b","5c","5d","5e","5f","60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f","70","71","72","73","74","75",
                  "76","77","78","79","7a","7b","7c","7d","7e"];
	return [" ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","@","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","[","]","^","_","`","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","{","|","}","~"]; 
}
 
string AsciiToKey(string str) {
 
    integer itra;
    string hexStr;
    for(itra=0; itra<llStringLength(str); itra++) {
        hexStr += llList2String(cSelect(1), llListFindList(cSelect(0), (list)llGetSubString(str, itra, itra)));
    }
    while(itra<32) {
        hexStr += "0";
        itra++;
    }
 
    return llGetSubString(hexStr,0,7) + "-" + llGetSubString(hexStr,8,11)+ "-" + llGetSubString(hexStr,12,15)+ "-" + llGetSubString(hexStr,16,19)+ "-" + llGetSubString(hexStr,20,31);
}
 
string KeyToAscii(key k) {
 
	integer itra;
	string strKey;
	for(itra=0; itra<llStringLength(k); itra++) {
		if(llGetSubString(k, itra, itra) != "-")
			strKey += llGetSubString(k, itra, itra);
	}
	string pureKey;
	for(itra= 0; itra<llStringLength(strKey); itra+ =2) {
		if(llGetSubString(strKey, itra, itra+1) != "00")
			pureKey += llGetSubString(strKey, itra, itra+1);
	}
	string asciiStr;
	for(itra= 0; itra<llStringLength(pureKey); itra+ =2) {
		asciiStr += llList2String(cSelect(0), llListFindList(cSelect(1), (list)llGetSubString(pureKey, itra, itra+1)));
	}
 
	return asciiStr;
}
 
default {
    state_entry() {
 	llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_BOX, 
                            PRIM_HOLE_DEFAULT,  // hole_shape
                            <0.00, 1.0, 0.0>,   // cut
                            0.0,                // hollow
                            <0.0, 0.0, 0.0>,    // twist
                            <1.0, 1.0, 0.0>,    // top_size
                            <0.0, 0.0, 0.0>,     // top_Shear
                            PRIM_SIZE,
                            <0.01, 0.01, 0.01>,  // size
                            PRIM_COLOR,
                            ALL_SIDES,
                            <0.0, 0.0, 0.0>,
                            0.00 
 
                      ]);
         llSetObjectName(llGetKey());
    }
 
    link_message(integer sender_num, integer num, string str, key id) {
    	if(num < 0 || num > 6 || llStringLength(str) > 16) return;
    	if(id == "@push") {
    		llSetTexture((key)AsciiToKey(str), num);
    		jump stored;
    	}
    	llMessageLinked(LINK_ALL_OTHERS, num, KeyToAscii(llGetTexture(num)), "@pull");
@stored;	
    }
}

Fast Texture Caching

This is a texture cacher that quickly loads some textures on a 10-faced hidden primitive XyzzyText 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 and not trash it.

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

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
//////////////////////////////////////////////////////////
// Requires: link message, string set to "kpre", string of 
// texture keys separated by commas.
// Provides: link message, string set to "kok" when ready.
//////////////////////////////////////////////////////////
// About: A script to cache textures and keep them alive.
//////////////////////////////////////////////////////////
list slides = [];
integer gitra = 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 ]);
    }
    timer() {
        llSetTimerEvent(0);
        if(gitra == llGetListLength(slides)) {
            gitra = 0;
        }
        integer itra=llGetNumberOfSides()-1;
        do {
            if(llList2Key(slides, gitra+itra))
                llSetTexture(llList2Key(slides, gitra+itra), itra);
        } while(--itra>-1);
        ++gitra;
        llSetTimerEvent(1.01-llGetRegionTimeDilation());
    }
    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 as discussed in our article Wizardry and Steamworks/Randomness, Entropy and Statistics.

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
//////////////////////////////////////////////////////////
// Requires: a limit.
// Provides: true random number between [0, max) or (-max, 0].
//////////////////////////////////////////////////////////
integer FPSrand(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 and no know them beforehand.

    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>=0);
@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 as discussed in our article Wizardry and Steamworks/Randomness, Entropy and Statistics.

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: 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 SplitLine by Huney Jewell, with the particularity that wasSpaceWrap 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

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: 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

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 for LSL as described in Wizardry and Steamworks/StaX.

wasStaX_GetNodeValue

/////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: 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;
}

wasStaX_SetNodeValue

/////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: 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.

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: 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):

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