Intercom

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.

About

Changelog

  • 13 November 2012

Reduced all the scripts to one single script.

  • 26 August 2011

Fix: Changed touch() to touch_start(). Simplified article and forked the Distributed Primitive Database.

  • 14 August 2011

Considerable changes to all scripts. Fixed and found a better way for missing caps. Added compression from Becky Pippen so that we can store up to 6 URLs in pseudo-permanent storage.

  • 13 August 2011

Implemented kickstart based on minimal permanent storage (once an intercom restarts it now automatically tries to connect to some stored URLs). Added password verification for linking up intercoms. Removed the master intercom feature - this is not really what it was meant to be. Added configurable reconnect attempts.

  • 12 August 2011

Initial release.

News

At 1am on 30th of August I managed to cross-link the Distributed Primitive Database between the OSGrid and Linden Lab's SecondLife by using an altered version of the scripts. I have also adapted the Intercom and managed to cross-chat between the OSGrid and SecondLife by using the same principle described here. It seems that this technique will not only work cross-region, however it will also work cross-grid.

I run an OpenSim estate, hooked up the the OSGrid. I have altered the code in the Distributed Primitive Database as well as in the Intercom version by knocking out the URL checks in the isValidURL() function that I wrote. For compatibility with OSL, I also had to knock out the jumps, probably a case of raptors again. I followed the same procedure described in both articles, I added the URL of a prim on the OSGrid to a primitive on the SecondLife grid. After a few minutes, replication started amongst the primitives and pretty soon the primitive on the OSGrid already replicated all the data I had on the SecondLife grid. This rocks. :-)

What does this do?

User answer:

  • it allows you to talk to several people across regions.

Developer answer:

  • it allows you to send messages from primitive to primitive grid-wide and across region boundaries supporting a star-type distributed replication system.

Practical Applications

Here are some examples of what this script will be able to do for you:

  • You could link up two dance clubs, and have the messages from one club be relayed to the other club.
  • You can have two groups of people in different regions talk to each-other as if they were in the same region via this script.
  • You could relay / broadcast messages from primitives grid-wide and across region boundaries.
  • You could use this to talk to somebody who is in another region on a different land parcel.
  • You could use this to talk to an arbitrary number of people grid-wide and across region boundaries working pretty much like a group telephone.
  • You could maintain a persistent SL-based database benefiting from data replication.
  • Multi-owned primitives containing the intercom system may be linked together: somebody would just have to give you 1 (one) URL from their network and your network and theirs would fusion together broadcasting messages across both networks.

Motivation

The main motivation for this script was to set up an intercom so I could talk to several people in different regions as if we were in the same region and in chat range. Currently there is no way to send a message from primitive to primitive if they are in different regions. I have also been thinking about ways to store data persistently in SL and I thought that if one would have build rights in different regions, one could set up a primitive in each region and then each primitive would relay messages to all the others which will in turn store that data and replicate it based on a timer. That way, if a region containing one of the primitives goes down due to a restart, it would be sufficient to add its address to one of the other primitives and it would register on all the primitives grid-wise, additionally sending the full list of addresses to the primitive that just went down. Either way, for a sufficiently large number of primitives, one per region, it is unlikely that they would all fail at the same time and break the replication loop.

How it Works

The script requests an URL from SL whenever it is reset or the region changes. It then send this URL to the master intercom. The master intercom could be any URL from a different intercom. You add one or several URLs to different intercoms by using the touch menu. The scripts will start replicating the URLs between each-other. Whenever there is some data to be sent, it will be sent by a different script to all the URLs that have replicated and thus broadcast the data across regions.

More precisely formulated:

Following the same algorithm, and having several primitives, say N primitives, named for example's sake, PRIMITIVE_1, PRIMITIVE_2 to PRIMITIVE_N in N different regions, even if a PRIMITIVE_N would go down because of a SIM restart, when that SIM comes back up, it would be sufficient to add the new URL, URL_N of PRIMITIVE_N to ANY primitive in the chain PRIMITIVE_1, PRIMITIVE_2 to PRIMITIVE_N-1, so that after a while PRIMITIVE_N will have obtained the full list of primitive URLs of all the other primitives PRIMITIVE_1, PRIMITIVE_2 to PRIMITIVE_N in the list as well as replicating its own URL_N to all other primitives in the chain PRIMITIVE_1, PRIMITIVE_2 to PRIMITIVE_N-1.

A simple example with two primitives:

  • Suppose that a primitive PRIMITIVE_1 containing this script has an URL of the form http://URL_1.
  • Suppose that another primitive PRIMITIVE_2 in a different region has an URL of the form http://URL_2.
  • When you add http://URL_2 to PRIMITIVE_1, then the URL http://URL_2 will register with PRIMTIVE_1.
  • After http://URL_2 is registered with PRIMTIVE_1, PRIMITIVE_1 will start sending its list of URLs to PRIMITIVE_2. This will have the effect, that PRIMITIVE_2 will also obtain the URL of PRIMITIVE_1.

Setting Up

  • The system contains of two essential scripts and a custom-self made script (I also provide some snippets). The essential scripts are the URL RELAY script, the MESSAGE RELAY script and the STORAGE PLUGIN script. Drop all these scripts in as many primitives in as many different regions as you like. Let us name them PRIMITIVE_1 in REGION_1, PRIMITIVE_2 in REGION_2 and so on...
  • Get the URL of PRIMITIVE_2, PRIMITIVE_3, etc... by using the "Get My URL" option on each of the primitives PRIMITIVE_2 in REGION_2, PRIMITIVE_3 in REGION_3, and so on...
  • Add these URLs to PRIMITIVE_1 using the "Add URL" dialog button. Instructions will be provided on the main chat.
  • Optionally add a plug-in script to all the primitives, PRIMITIVE_1, PRIMITIVE_2 to PRIMITIVE_N. The provided plug-in script CHAT RELAY script just relays chat messages from the main to all the other main chats where the other primitives are rezzed.

Now, every one of the intercom primitives located in different regions should contain the following scripts in their inventory:

URL RELAY
MESSAGE RELAY
STORAGE PLUGIN
CHAT PLUGIN
  • Wait for all the URLs to replicate between the primitives. You can see how many URLs replicated in a certain primitive, say PRIMITIVE_X by going to REGION_X and clicking the prim and choosing the Intercom options. The script will return a list of URLs it currently is aware of.
  • Type something on main chat and all primitives should echo what you typed in all the regions they are placed in.

Configuration Settings

Most of the settings for the intercom URL RELAY SCRIPT are good to go however, you might want to tweak different settings to your liking:

integer SYNC_TIME = 10;
list ACCESS_LIST = [ "Kira Komarov", "Lance Lenoirre" ];
string INTERCOM_PASSWORD = "49c11b5dfa51597b3021578810a1ebd2";

Here is a description of what each option does:

  • The SYNC_TIME is the time interval in seconds between replications. Periodically, based on this time interval, the primitive will try to send all its URLs to all the other primitives it has in its list. It is usually fairly decent to keep this at 60 seconds since it is unlikely that ALL the regions containing these primitives will go down in a 60 second interval.
  • The ACCESS_LIST contains the usernames of avatars which are able to control the script (people who are able to add URLs, get the primitive's own URL, see the list of URLs and reset the intercom).
  • The INTERCOM_PASSWORD is a list of passwords that your intercom primitive will accept and send when connecting to another intercom. This will allow you to isolate your intercom network from other people's networks. Please change this password to something else. I would personally google for an online md5 hash generator and get a password based on that. Regardless what you choose, the password should NOT contain punctuation marks or spaces!

Script: URL RELAY (NEEDED)

//////////////////////////////////////////////////////////
// [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.              //
//////////////////////////////////////////////////////////
 
// Cross-region intercom: URL RELAY SCRIPT
 
//////////////////////////////////////////////////////////
//------------------- CONFIGURATION --------------------//
//////////////////////////////////////////////////////////
 
// Time in seconds between the Intercom replication attempts.
// This determines how fast the current prim/intercom will
// attempt to connect to other prims/intercoms in your
// network. Keep this at a sensible value, way beyond a
// setting of 1. Do NOT be greedy and go too low on this
// or you will overclock the script till it breaks.
// Sensible values are well beyond 60s and it should be that
// way for slow to moderate storage.
integer SYNC_TIME = 10;
// Additional access list for shared access.
list ACCESS_LIST = [ "Kira Komarov", "Lance Lenoirre" ];
// This is your whole intercom network password. Nobody would
// be able to hook up to your network unless they know this
// password. Make sure it is something unique. If you want a
// good password, google for an md5 hash generator and use that.
// ALL the prims in your intercom network must have the same
// password that you enter here. It can be anything you choose
// but WITHOUT SPACES and without PUNCTUATION MARKS/SYMBOLS.
string INTERCOM_PASSWORD = "49c11b5dfa51597b3021578810a1ebd2";
 
//////////////////////////////////////////////////////////
//--------------------- INTERNALS ----------------------//
//////////////////////////////////////////////////////////
integer intercomON = 1;
integer menuHandle = 0;
integer registerHandle = 0;
list urlBeacons = [];
string selfURL = "";
list firstBeaconQueue = [];
list secondBeaconQueue = [];
 
integer isValidURL(string URL)
{
    if (~llSubStringIndex(URL, "http://") &&
        ~llSubStringIndex(URL, "lindenlab.com") &&
        ~llSubStringIndex(URL, "cap"))
    {
        return TRUE;
    }
 
    return FALSE;
}
 
release_and_request_url()
{
    llReleaseURL(selfURL);
    selfURL = "";
    llRequestURL();
}
 
default
{
    on_rez(integer pin)
    {
        release_and_request_url();
    }
 
    changed(integer change)
    {
        if (change & (CHANGED_REGION_START | CHANGED_REGION))
            release_and_request_url();
    }
 
    state_entry()
    {
        release_and_request_url();
        llMessageLinked(LINK_THIS, 0, "", "@get_kickstart");
        llSetTimerEvent(SYNC_TIME);
    }
 
    timer()
    {
        llSetTimerEvent((float)FALSE);//  stop timer
 
        integer firstBeaconQueueLength = llGetListLength(firstBeaconQueue);
        integer secondBeaconQueueLength = llGetListLength(secondBeaconQueue);
        string firstBeaconInFirstBeaconQueue = llList2String(firstBeaconQueue, 0);
        string firstBeaconInSecondBeaconQueue = llList2String(secondBeaconQueue, 0);
 
        if (!firstBeaconQueueLength)
        {
            firstBeaconQueue = urlBeacons;
            secondBeaconQueue = llDeleteSubList(secondBeaconQueue, 0, 0);
        }
        if (!secondBeaconQueueLength)
        {
            secondBeaconQueue = urlBeacons;
        }
        if (isValidURL(firstBeaconInFirstBeaconQueue) && isValidURL(firstBeaconInSecondBeaconQueue))
        {
            llHTTPRequest(firstBeaconInFirstBeaconQueue,
                [HTTP_METHOD, "POST"],
                firstBeaconInSecondBeaconQueue + " " + INTERCOM_PASSWORD);
        }
        firstBeaconQueue = llDeleteSubList(firstBeaconQueue, 0, 0);
        llMessageLinked(LINK_THIS, 0, selfURL, "@set_kickstart=" + llList2CSV(urlBeacons));
 
        llSetTimerEvent(SYNC_TIME);
    }
 
    touch_start(integer total_number)
    {
        key id = llDetectedKey(0);
        string name = llDetectedName(0);
 
        key owner = llGetOwner();
 
        if (id != owner && !~llListFindList(ACCESS_LIST, [name]))
            return;
 
        integer comChannel = ((integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        menuHandle = llListen(comChannel, "", id, "");
        llDialog(id,
            "Options description:\n\nAdd URL: Use this to add new Intercoms.\n\n"
                + "My URL: Use this to get the address of this Intercom.\n\n"
                + "List URLs: Use this to list all the linked intercoms.\n\n"
                + "Reset: Use this to permanently reset the current intercom since resetting "
                + "the script will not unlink it from the network.\n\n"
                + "On/Off: This will turn the intercom on and off. It will remain connected "
                + "to the network but will not receive or broadcast messages.",
            ["[ Add URL ]", "[ My URL ]", "[ List URLs ]", "[ Reset ]", "[ On ]", "[ Off ]"],
            comChannel);
    }
 
    listen(integer channel, string name, key id,string message)
    {
        if (message == "[ On ]")
        {
            intercomON = 1;
            llInstantMessage(id, "Intercom is now: ON");
            jump close_chans;
        }
        if (message == "[ Off ]")
        {
            intercomON = 0;
            llInstantMessage(id, "Intercom is now: OFF");
            jump close_chans;
        }
        if (message == "[ Reset ]")
        {
            llSetTimerEvent((float)FALSE);//  stop timer
            llMessageLinked(LINK_THIS, 0, "", "@wipe_storage");
            llResetScript();
            return;
        }
        if (message == "[ Add URL ]")
        {
            llInstantMessage(id, "Please paste an URL on channel " + (string)89
                + " in order to register it with the system by typing:\n/" + (string)89
                + " URL\nWhere URL is the URL of another intercom.");
            registerHandle = llListen(89, "", id, "");
            jump close_menu;
        }
        if (message == "[ My URL ]")
        {
            if (selfURL == "")
            {
                llInstantMessage(id, "I don't have an URL registered yet.");
                jump close_chans;
            }
            llInstantMessage(id, "My URL is: " + selfURL);
            jump close_chans;
        }
        if (message == "[ List URLs ]")
        {
            integer lengthOfurlBeaconList = llGetListLength(urlBeacons);
 
            if (!lengthOfurlBeaconList)
            {
                llInstantMessage(id, "No beacons registered.");
                jump close_chans;
            }
            llInstantMessage(id, "----- INTERCOMS -----");
            integer itra;
            do
            {
                llInstantMessage(id, llList2String(urlBeacons, itra));
            }
            while (++itra < lengthOfurlBeaconList)
            llInstantMessage(id, "----- INTERCOMS -----");
            jump close_chans;
        }
        if (chan != 89)
        {
            return;
        }
        mes = llList2String(llParseString2List(message, [" "], [""]), 0);
        if (!isValidURL(mes))
        {
            llInstantMessage(id, "Bad formatted URL");
            return;
        }
        if (~llListFindList(urlBeacons, [message]))
        {
            llInstantMessage(id, "URL already exists.");
            jump close_chans;
        }
        urlBeacons += [message];
        llInstantMessage(id, "URL: " + message + " has been registered.");
@close_chans;
        llListenRemove(registerHandle);
@close_menu;
        llListenRemove(menuHandle);
 
    }
 
    link_message(integer sender_num, integer num, string str, key id)
    {
        if (!intercomON)
        {
            jump section_kickstart;
        }
        if (id != "@broadcast_request")
        {
            jump section_kickstart;
        }
        integer foundOwnURL = llListFindList(urlBeacons, [selfURL]);
 
        list otherURLs = llDeleteSubList(urlBeacons, foundOwnURL, foundOwnURL);
        if (!llGetListLength(otherURLs))
        {
            return;
        }
        llMessageLinked(LINK_THIS, 0, llList2CSV(otherURLs), "@send_message=" + str);
@section_kickstart;
        if (id != "@kickstart_beacons")
        {
            return;
        }
        list kickstartBeacons = llCSV2List(str);
        integer itra;
        do
        {
            if (~llListFindList(urlBeacons, (list)llList2String(kickstartBeacons, itra)) || !isValidURL(llList2String(kickstartBeacons, itra)))
            {
                jump continue;
            }
            urlBeacons += llList2String(kickstartBeacons, itra);
@continue;
        }
        while (++itra < llGetListLength(kickstartBeacons));
 
    }
 
    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED)
        {
            selfURL = body;
            urlBeacons += [body];
            return;
        }
        if (method == URL_REQUEST_DENIED)
        {
            release_and_request_url();
            return;
        }
        if (method == "POST")
        {
            list postPayload = llParseString2List(body, [" "], [""]);
            if (!isValidURL(llList2String(postPayload, 0)))
            {
                return;
            }
            if (llList2String(postPayload, 1) != INTERCOM_PASSWORD)
            {
                return;
            }
            if (~llListFindList(urlBeacons, (list)llList2String(postPayload, 0)))
            {
                return;
            }
            urlBeacons += [llList2String(postPayload, 0)];
            llHTTPResponse(id, 200, "OK");
            return;
        }
        if (method == "PUT")
        {
            if (!intercomON)
            {
                return;
            }
            llSay(PUBLIC_CHANNEL, body);
            llHTTPResponse(id, 200, "OK");
            return;
        }
    }
 
    http_response(key id, integer status, list metadata, string body)
    {
        if (status == 404)
        {
            string badBeaconKey = llList2String(llParseString2List(body, [": ", "'"], [""]), 1);
            integer itra;
            for(itra = -1; itra < llGetListLength(urlBeacons); ++itra)
            {
                if (~llSubStringIndex(llList2String(urlBeacons, itra), badBeaconKey))
                    urlBeacons = llDeleteSubList(urlBeacons, itra, itra);
            }
            for(itra = -1; itra < llGetListLength(firstBeaconQueue); ++itra)
            {
                if (~llSubStringIndex(llList2String(firstBeaconQueue, itra), badBeaconKey))
                    firstBeaconQueue = llDeleteSubList(firstBeaconQueue, itra, itra);
            }
            for(itra = -1; itra < llGetListLength(secondBeaconQueue); ++itra)
            {
                if (~llSubStringIndex(llList2String(secondBeaconQueue, itra), badBeaconKey))
                    secondBeaconQueue = llDeleteSubList(secondBeaconQueue, itra, itra);
            }
        }
    }
}

Script: MESSAGE RELAY (NEEDED)

//////////////////////////////////////////////////////////
// [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.              //
//////////////////////////////////////////////////////////
 
// Cross-region intercom: MESSAGE RELAY
 
//////////////////////////////////////////////////////////
//--------------------- INTERNALS ----------------------//
//////////////////////////////////////////////////////////
 
list beaconQueue;
list messageQueue;
 
integer isValidURL(string URL)
{
    if(~llSubStringIndex(URL, "http://") &&
        ~llSubStringIndex(URL, "lindenlab.com") &&
        ~llSubStringIndex(URL, "cap"))
    {
        return TRUE;
    }
 
    return FALSE;
}
 
default
{
    state_entry()
    {
        llSetTimerEvent(1.0);
    }
 
    link_message(integer sender_num, integer num, string str, key id)
    {
        list msg = llParseString2List(id, ["="], [""]);
        if(llList2String(msg, 0) != "@send_message")
        {
            return;
        }
        list broadcastBeacons = llCSV2List(str);
        if(!llGetListLength(broadcastBeacons))
        {
            return;
        }
        integer itra;
        for(itra = 0; itra < llGetListLength(broadcastBeacons); ++itra)
        {
            if(~llListFindList(beaconQueue, llList2List(broadcastBeacons, itra, itra)))
            {
                jump continue;
            }
            if(!isValidURL(llList2String(broadcastBeacons, itra)))
            {
                jump continue;
            }
            beaconQueue += llList2List(broadcastBeacons, itra, itra);
@continue;
        }
        messageQueue += llList2List(msg, 1, 1);
 
    }
 
    timer()
    {
        if(!llGetListLength(messageQueue))
        {
            jump reschedule;
        }
        if(isValidURL(llList2String(beaconQueue, 0)) && isValidURL(llList2String(beaconQueue, 0)))
        {
            llHTTPRequest(llList2String(beaconQueue, 0), [HTTP_METHOD, "PUT"], llList2String(messageQueue, 0));
        }
        beaconQueue = llDeleteSubList(beaconQueue, 0, 0);
        if(!llGetListLength(beaconQueue))
        {
            messageQueue = llDeleteSubList(messageQueue, 0, 0);
        }
@reschedule;
        llSetTimerEvent(1.0);
    }
}

Script: STORAGE PLUGIN (NEEDED)

//////////////////////////////////////////////////////////
// [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.              //
//////////////////////////////////////////////////////////
 
// Cross-region intercom: STORAGE PLUGIN
 
//////////////////////////////////////////////////////////
/////////////// BEGIN IMPORTED BLOCK /////////////////////
 
// This compression algorithm is created by Becky Pippen and is available @
// https://wiki.secondlife.com/wiki/User:Becky_Pippen/Text_Storage
 
//////////////////////////////////////////////////////////
//   Becky Pippen, 2009, contributed to Public Domain   //
//////////////////////////////////////////////////////////
 
string hexChar2(integer n)
{
    string hexChars = "0123456789abcdef";
    return llGetSubString(hexChars, n >> 4, n >> 4) +llGetSubString(hexChars, n & 0xf, n & 0xf);
}
 
integer charToUnicodeIdNumber(string c)
{
    integer cInt = llBase64ToInteger(llStringToBase64(c));
    if (!(cInt & 0x80000000))
    {
        cInt = cInt >> 24;
    }
    else
    {
        if ((cInt & 0xe0000000) == 0xc0000000)
        {
            cInt = ((cInt & 0x1f000000) >> 18) | ((cInt & 0x003f0000) >> 16);
        }
        else
        {
            cInt = ((cInt & 0x0f000000) >> 12) | ((cInt & 0x003f0000) >> 10) | ((cInt & 0x00003f00) >> 8);
        }
     }
     return cInt;
}
 
string encode15BitsToChar(integer num)
{
    if (num < 0 || num >= 0x8000)
    {
        return "�";
    }
    num += 0x1000;
    return llUnescapeURL("%" + hexChar2(0xe0 + (num >> 12)) + "%" + hexChar2(0x80 + ((num >> 6) & 0x3f)) + "%" + hexChar2(0x80 + (num & 0x3f)));
}
 
integer decodeCharTo15Bits(string ch)
{
    string utf8 = llEscapeURL(ch);
    return (((integer)("0x" + llGetSubString(utf8, 1, 2)) & 0x1f) << 12) + (((integer)("0x" + llGetSubString(utf8, 4, 5)) & 0x3f) << 6) + ((integer)("0x" + llGetSubString(utf8, 7, 8)) & 0x3f) - 0x1000;
}
 
string compressAscii(string s)
{
     integer len = llStringLength(s);
     if (len % 2)
     {
        s += " ";
        ++len;
     }
     string encodedChars;
     integer i;
     for (i = 0; i < len; i += 2)
     {
         encodedChars += encode15BitsToChar(
                 charToUnicodeIdNumber(llGetSubString(s, i, i)) << 7 |
                 charToUnicodeIdNumber(llGetSubString(s, i+1, i+1)));
     }
     return encodedChars;
}
 
string uncompressAscii(string s)
{
    string result;
    integer len = llStringLength(s);
    integer i;
    for (i = 0; i < len; ++i)
    {
        integer cInt15 = decodeCharTo15Bits(llGetSubString(s, i, i));
        result += llUnescapeURL("%" + hexChar2(cInt15 >> 7) +
                                "%" + hexChar2(cInt15 & 0x7f));
    }
    return result;
}
 
//////////////// END IMPORTED BLOCK //////////////////////
//////////////////////////////////////////////////////////
 
integer isValidURL(string URL)
{
    if(~llSubStringIndex(URL, "http://") &&
        ~llSubStringIndex(URL, "lindenlab.com") &&
        ~llSubStringIndex(URL, "cap"))
    {
        return TRUE;
    }
 
    return FALSE;
}
 
list getKickstartURLs()
{
    list kickStartURLs = llCSV2List(llList2String(llGetPrimitiveParams([PRIM_TEXT]), 0));
    integer itra;
    list kickstartBeacons = [];
    for(itra=0; itra<llGetListLength(kickStartURLs); ++itra)
    {
        // Decompression by Becky Pippen, 2009
        string newBeacon = uncompressAscii(llList2String(kickStartURLs, itra));
        if(!isValidURL(newBeacon))
        {
            jump continue;
        }
        kickstartBeacons += newBeacon;
@continue;
    }
    return kickstartBeacons;
}
 
setKickstartURLs(list beacons)
{
    list rndIndices = [];
    list selectedBeacons = [];
    integer itra;
    for(itra=0; itra<llGetListLength(beacons); ++itra)
    {
        rndIndices += itra;
    }
    while(llGetListLength(rndIndices) && llGetListLength(selectedBeacons) < 6)
    {
        integer rndPick = llList2Integer(rndIndices, (integer) llFrand(llGetListLength(rndIndices)));
        rndIndices = llDeleteSubList(rndIndices, llListFindList(rndIndices, (list)rndPick), llListFindList(rndIndices, (list)rndPick));
        // Compression by Becky Pippen, 2009
        selectedBeacons += compressAscii(llList2String(beacons, rndPick));
    }
    llSetLinkPrimitiveParamsFast(LINK_THIS,
        [PRIM_TEXT, llList2CSV(selectedBeacons), ZERO_VECTOR, 0.0]);
}
 
default
{
    link_message(integer sender_num, integer num, string str, key id)
    {
        if(id != "@get_kickstart")
        {
            jump section_kickstart;
        }
        llMessageLinked(LINK_THIS, 0, llList2CSV(getKickstartURLs()), "@kickstart_beacons");
        return;
@section_kickstart;
        list msg = llParseString2List(id, ["="], [""]);
        if(llList2String(msg, 0) != "@set_kickstart")
        {
            jump section_wipe;
        }
        setKickstartURLs(llDeleteSubList(llCSV2List(llList2String(msg, 1)), llListFindList(llCSV2List(llList2String(msg, 1)), (list) str), llListFindList(llCSV2List(llList2String(msg, 1)), (list) str)));
        return;
@section_wipe;
        if(id != "@wipe_storage")
        {
            return;
        }
        llSetLinkPrimitiveParamsFast(LINK_THIS,
            [PRIM_TEXT, "", ZERO_VECTOR, 0.0]);
    }
}

Script: CHAT PLUGIN (Will be optional, needed for now.)

//////////////////////////////////////////////////////////
// [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.              //
//////////////////////////////////////////////////////////
 
default
{
    state_entry()
    {
        llListen(PUBLIC_CHANNEL, "", NULL_KEY, "");
    }
 
    listen(integer chan,string name,key id,string mes)
    {
        if(id!=llGetKey())
        {
            llMessageLinked(LINK_THIS, 0, name + ": " + mes, "@broadcast_request");
        }
    }
}

TODO

A list for myself:

  • create a video to illustrate how this works, it's simple but a video is worth a million words.
  • add passwords for individual networks so that a leaked URL will not start to fusion networks together.
  • add some memory / limits checking, especially for large networks.
  • create some persistent storage so that when a primitive goes down, it can automatically re-join the network without user interaction.
  • create a script allowing to store distributed data rather than just relaying chat messages. Now in the works @ Distributed Primitive Database
  • needs some extensive 3-way, 4-way testing.