Bubble Trapper

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.

ChangeLog

  • 18 December 2011

Fixed to allow/accept names longer than 24 characters. Added feature to trap ALL. Added PRIM_TEMP_ON_REZ so that bubbles will die in all cases.

Description

Similar to cage-trappers, sometimes it might be useful to highlight an avatar by surrounding them in a bubble. Different from cage-trappers, this script is meant to simulate trapping somebody in a water bubble and to explode a few seconds after it reaches its destination. Originally, I created it for Juvon Gibbs as a commission and was meant for djing events, in order to thank people who tipped the DJ.

The name "Trapper" is misleading since these scripts do not effectively trap an avatar into the bubble since the bubble itself is phantom, allowing avatars to pass through it at any time. Moreover, the bubble bursts with a 2 second delay after it has reached the chosen avatar's position by using llDie().

The script also uses at_target() and not_at_target() to make the bubble homing an go after the avatar instead of just the last position the avatar was found to be.

I am posting it here since it uses an alternative way to rez an object and to pass an avatar's name to that object.

Hash a String to a Number

Usually, whenever you rez an object, you also have a script within that object and you need to pass data from the rezzer to the rezzed object. Conventionally, one would do that with some variation of llRegionSay()/llWhisper(). However, that usually implies that the script in the rezzed object must listen() on a channel and the rezzer might even have to use the timer() event in order to make sure that the rezzed object has received the data.

For example:

llRezObject("object name", ...);
llRegionSay(channel, data);

this might not work as expected since there is a delay between rezzing the object, the object rezzed setting up a listen() and the llRegionSay() that the rezzer uses to pass the data. The workaround is to put llRegionSay() inside a timer() and continuously broadcast the data to make sure that the rezzed object has received it.

Here might be a (risky) alternative:

The function llRezObject(), takes as last parameter a number which will be passed to the object that will be rezzed. The problem is that, frequently, you need to pass a string and the fact that llRezObject() can only pass an integer does not help. In order to do that, we can try to hash the string to a number. For example, this script simply adds up the alphabetic positions of the letters in a string and uses that number to identify the string:

integer Name2Number(string name) {
    list alpha = ["*", "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 itra;
    integer ahash;
    for(itra=llStringLength(name), ahash=0; itra>-1; --itra) {
        ahash += llListFindList(alpha, (list)llToLower(llGetSubString(name, itra, itra)));
    }
    return ahash;
}
...
llRezObject("object name",...,Name2Number(avatar name));

When the rezzed object rezzes, it uses the same algorithm to compute the hashes of the avatar names it finds around it. When the two numbers are identical, the rezzed object has found the avatar chosen by the rezzer.

There are certain problems with that, for example:

  • All palindrome strings will fail.
  • It does not account for ordering, if two avatars use exactly the same letters in their names, the hash doesn't take that into account and considers them to be the same person.
  • Suppose an avatar is named "c", and suppose another avatar is named "ab". The two avatars are one in the same when using this hash.

I am unsure how frequent the problems above might appear in reality. Of course, the probability of encountering one of the problems above rises with the number of avatars to be scanned. One could strengthen the algorithm above, by using capital letters as well.

However, In case one has access to the key of the object, one might use:

integer Key2Number(key objKey) {
  return ((integer)("0x"+llGetSubString((string)objKey,-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
}
...
llRezObject("object name",...,Key2Number(some key));

Which is taken from llDialog and usually meant to generate channel numbers. However, it is still just a hash of a key, resulting in a number.

The rezzed object, will have to scan the vicinity again for object or avatars and compare the number it received (in the on_rez() event) by using a hashing algorithm.

By doing that, you would have at least:

  1. freed up the timer() event in the rezzer allowing you to use it for something else.
  2. got rid of llRegionSay()/llWhisper().
  3. freed up the listen() event in the rezzed object.

Setting up the Bubble Trapper

  1. Create a sphere primitive and drop the code and drop the [K] Bubble Trapper - Homing and optionally the [K] Bubble Trapper - Animation. You can also change the the texture and transparency. Save the scripts and take the bubble primitive to your inventory.
  2. Create another primitive which will be the rezzer and drop the [K] Bubble Trapper - Launcher script into it.
  3. Open the rezzer primitive you created at point 2 and add the bubble primitive you created at point 1 inside it.

You are set. Just click the primitive and select the avatar to launch the bubble at.

Code: [K] Bubble Trapper - Homing

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
integer Name2Number(string name) {
    list alpha = ["*", "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 itra;
    integer ahash;
    for(itra=llStringLength(name), ahash=0; itra>-1; --itra) {
        ahash += llListFindList(alpha, (list)llToLower(llGetSubString(name, itra, itra)));
    }
    return ahash;
}
 
integer hidTarget = -1;
integer nTarget = -1;
key avKey = NULL_KEY;
 
default
{
    at_target(integer tnum, vector targetpos, vector ourpos) {
        if(avKey == NULL_KEY) return;
        avKey = NULL_KEY;
        llSetTimerEvent(2);
    }
    not_at_target() {
        llSleep(1);
        llTargetRemove(nTarget);
        vector pos = llList2Vector(llGetObjectDetails(avKey, [OBJECT_POS]), 0);
        nTarget = llTarget(pos, 1.0);
        llMoveToTarget(pos, 0.5);
    }
    timer() {
        llSetTimerEvent(0);
        llSetStatus(STATUS_PHYSICS, FALSE);
        vector size  = llGetScale();
        float xsize = size.z + size.z/2;
        float itra;
        for(itra=size.z; itra<xsize; ++itra) {
            llSetScale(<itra, itra, itra>);
            llSleep(llGetRegionTimeDilation()+0.1);
        }
        llPlaySound("ff0600f0-d8f4-20f6-52ec-a03bc520fee3", 1.0);
        llDie();
    }
    on_rez(integer param) {
        hidTarget = param;
        llSetPrimitiveParams([PRIM_TEMP_ON_REZ, 1]);
        llPreloadSound("ff0600f0-d8f4-20f6-52ec-a03bc520fee3");
        llSensorRepeat("", "", AGENT, 96, TWO_PI, llGetRegionTimeDilation()); 
    }
    sensor(integer num_detected) {
        integer itra;
        for(itra=0; itra<num_detected; ++itra) {
            avKey = llDetectedKey(itra);
            if(Name2Number(llGetSubString(llKey2Name(avKey), 0, 23)) == hidTarget) {
                llSensorRemove();
                vector size = llGetAgentSize(avKey);
                size.z += 0.17 + 0.5;
                llSetScale(<size.z, size.z, size.z>);
                llSetStatus(STATUS_PHYSICS|STATUS_PHANTOM, TRUE);
                vector pos = llList2Vector(llGetObjectDetails(avKey, [OBJECT_POS]), 0);
                nTarget = llTarget(pos, 1.0);
                llMoveToTarget(pos, 0.5);
                return;
            }
        }
    }
}

Code: [K] Bubble Trapper - Launcher

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
list menu_items = [];
list key_items = [];
integer mitra = 0;
list cList = [];
 
list tgtNames = [];
integer comHandle = 0;
integer comChannel = 0;
 
list mFwd() {
    if(mitra+1>llGetListLength(menu_items)) return cList;
    cList = llListInsertList(llListInsertList(llListInsertList(llList2List(menu_items, ++mitra, (mitra+=9)), ["<= Back"], 0), ["[ ALL ]"], 1), ["Next =>"], 2);
    return cList;
} 
 
list mBwd() {
    if(mitra-19<0) return cList;
    cList = llListInsertList(llListInsertList(llListInsertList(llList2List(menu_items, (mitra-=19), (mitra+=9)), ["<= Back"], 0), ["[ ALL ]"], 1), ["Next =>"], 2);
    return cList;
}
 
init() {
    comChannel = ((integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
}
 
integer Name2Number(string name) {
    list alpha = ["*", "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 itra;
    integer ahash;
    for(itra=llStringLength(name), ahash=0; itra>-1; --itra) {
        ahash += llListFindList(alpha, (list)llToLower(llGetSubString(name, itra, itra)));
    }
    return ahash;
}
 
default {
 
    state_entry() {
        init();
    }
 
    on_rez(integer param) {
        llResetScript();
    }
 
    touch_start(integer num) {
        if(!llGetInventoryNumber(INVENTORY_OBJECT)) {
            llOwnerSay("I don't have an object loaded in my inventory. Please add an object and try again...");
            return;
        }
        llOwnerSay("Please wait, scanning for avatars...");
        llSensor("", "", AGENT, 64, TWO_PI);
    }
 
    sensor(integer num) {
        integer itra;
        for(itra=0, mitra=0, menu_items=[], tgtNames=[]; itra<num; ++itra) {
            key detectedAVKey = llDetectedKey(itra);
            vector detectedAVPos = llDetectedPos(itra);
            string detectedAVName = llDetectedName(itra);
            if(detectedAVKey != NULL_KEY && llVecDist(llGetPos(), detectedAVPos) < 64) {
                tgtNames += llGetSubString(detectedAVName,0,23);
                menu_items += llGetSubString(detectedAVName,0,23);
            }
        }
        if(!llGetListLength(menu_items)) {
            llOwnerSay("Sorry, I could not find anybody around to poof.");
            return;
        }
        cList = llListInsertList(llListInsertList(llListInsertList(llList2List(menu_items, mitra, (mitra+=9)), ["<= Back"], 0), ["[ ALL ]"], 1), ["Next =>"], 2);
        comHandle = llListen(comChannel+1, "", llGetOwner(), "");
        llDialog(llGetOwner(), "Please select an avatar to surround in the bubble from the list below:\n", cList, comChannel+1);
    }
 
    listen(integer channel, string name, key id, string message) {
        if(message == "<= Back") {
            llDialog(id, "Please browse the available avatars:\n", mBwd(), channel);
            return;
        }
        if(message == "Next =>") {
            llDialog(id, "Please browse the available avatars:\n", mFwd(), channel);
            return;
        }
        if(message == "[ ALL ]") {
            integer itra;
            for(itra=llGetListLength(tgtNames)-1; itra>=0; --itra) {
                llRezObject(llGetInventoryName(INVENTORY_OBJECT,0), llGetPos() + <0,0,1>, ZERO_VECTOR, ZERO_ROTATION, Name2Number(llList2String(tgtNames, itra)));
            }
            return;
        }
        if(channel == comChannel+1) {
            llListenRemove(comHandle);  
            llRezObject(llGetInventoryName(INVENTORY_OBJECT, 0), llGetPos() + <0,0,1>, ZERO_VECTOR, ZERO_ROTATION, Name2Number(llList2String(tgtNames, llListFindList(tgtNames, (list)message))));
            return;
        }
    }
}

Code: [K] Bubble Trapper - Animation

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
// Picked from:
// https://wiki.secondlife.com/wiki/LlSetTextureAnim
 
default
{
    state_entry() {
        llSetTextureAnim(ANIM_ON | SMOOTH | ROTATE | LOOP, ALL_SIDES,1,1,0, llFrand(TWO_PI), llFrand(2*TWO_PI));
    }
 
    on_rez(integer param) {
        llSetTextureAnim(ANIM_ON | SMOOTH | ROTATE | LOOP, ALL_SIDES,1,1,0, llFrand(TWO_PI), llFrand(2*TWO_PI));
    }
}