This script was tested and works on OpenSim version 0.7.4!

Changelog

13 November 2012

  • Reduced the system to only one script.

12 November 2012

  • Started to rework old parts of the code.
  • Made the scripts work with OpenSim by eliminating the Second Life specific quirks. The code should now work without modifications on OpenSim servers.
  • Eliminated Becky Pippen's compression since it was redundant and caused LSL implementation-specific problems.

26 August 2011

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.

About

The system bellow allows broadcasting a chat messages between simulators or grids. It can be used to say, mirror the local chat between two clubs on two different simulators or grids. The scripts are designed to be compatible with OpenSim and Second Life and they do not require any external hosting or additional technologies other than the Second Life and Open Sim platforms. This script is the Intercom version of the distributed_primitive_database.

Quickstart

  • Create two primitives, one on simulator A and one on simulator B.
  • Edit the script and change the INTERCOM_PASSWORD at the start of the script:
string INTERCOM_PASSWORD = "49c11b5dfa51597b3021578810a1ebd2";

The password can be anything that does not contain punctuation marks or spaces.

  • Drop the script into both primitives on simulator A and on simulator B.

Now you have two options:

  • [Add URLs Manually] You can add URLs on-the-fly by touching the intercom on one simulator, choosing [ My URL ] and then touching the other intercom, choosing [ Add URL ] and add the URL from the first intercom.
  • [Adding URLs using a Notecard] Instead of manually adding the URLs, you can create a notecard called Intercoms that contains a list of URLs line-by-line of other intercoms and drop that notecard into the same primitive that contains the script. Remember to leave a blank line after the URLs in the notecard or the script will not be able to detect the end of the notecard.

News

On 30th of August 2010 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. 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.

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.

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.
  • 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.
  • All URLs change when the script is reset. However, it is possible to couple the script with the permanent primitive URL script in order to maintain a permanent URL for your intercom. That way, when your intercom restarts, you will not have to announce everybody that your URL has changed. For large networks, this is not necessarily needed due to the fact that if you already maintain a list of URLs, your new URL will be replicated throughout the network.

Technical Design

The script requests an URL from SL whenever it is reset or the region changes. You add one or several URLs to different intercoms by listing them in a notecard or by using the touch menu. The intercoms will start replicating the URLs between each-other. Whenever there is some data to be sent, it will be sent to all the known URLs that have replicated and thus the messages will be broadcasted.

Region Restart Resistance

By default we have three simulators, two simulators containing an intercom (marked with green circles) and both intercoms know about each-other's URLs.

When the first intercom restarts, the second intercom does not know a valid address for the first intercom anymore. However, since we store the URLs in a notecard (or if the URL is provided by a third intercom on some other sim), the first intercom will know about the address of the second intercom.

At the point the first and second intercom synchronize their addresses and the second intercom finds out about the first intercom's new address.

Once the second intercom restarts, the first intercom does not have a valid URL for the second intercom. However, since we store the URLs in a notecard (or if the URL is provided by a third intercom on some other sim), the second intercom will know about the address of the first intercom.

At this point the second and first intercom synchronize their addresses and the first intercom finds out about the second intercom's new address.

Now that the restart wave has passed, the intercoms still know about each other's addresses.

Code

intercom.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2012 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
// Cross-region intercom: URL RELAY SCRIPT
 
//////////////////////////////////////////////////////////
//------------------- CONFIGURATION --------------------//
//////////////////////////////////////////////////////////
 
// 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 = TRUE;
integer menuHandle = 0;
integer registerHandle = 0;
list urlBeacons = [];
string selfURL = "";
list requestQueue = [];
key nQuery = NULL_KEY;
integer nLine = 0;
 
integer isValidURL(string URL) {
    if(llSubStringIndex(URL, "http://") >= 0) return TRUE;
    return FALSE;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2012 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
// http://grimore.org/fuss:lsl
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);
}
 
default {
    state_entry() {
        integer itra = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
        do {
            if(llGetInventoryName(INVENTORY_NOTECARD, itra) == "Intercoms")
            jump found_notecard;
        } while(--itra>0);
        llOwnerSay("No Intercoms notecard found, proceeding without one...");
        state intercom;
@found_notecard;
        nQuery = llGetNotecardLine("Intercoms", nLine);
    }
    dataserver(key id, string data) {
        if(id != nQuery) return;
        if(data == EOF) state intercom;
        if(data == "") jump next_line;
        if(!isValidURL(data)) jump next_line;
        urlBeacons += data;
@next_line;
        nQuery = llGetNotecardLine("Intercoms", ++nLine);
    }
    changed(integer change) {
        if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_INVENTORY))
            llResetScript();
    }
    on_rez(integer pin) {
        llResetScript();
    }
 
}
 
state intercom 
{
    state_entry() {
        llRequestURL();
        llSetTimerEvent(60);
        llSensorRepeat("", NULL_KEY, AGENT, .1, .1, 1);
        llListen(0, "", "", "");
    }
 
    touch_start(integer total_number) {
        if(llDetectedKey(0) != llGetOwner()) return;
        integer comChannel = ((integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        menuHandle = llListen(comChannel, "", llGetOwner(), "");
        llDialog(llGetOwner(), "Options description:\n\nAdd URL: Use this to add new Intercoms.\n\nMy URL: Use this to get the address of this Intercom.\n\nList URLs: Use this to list all the linked intercoms.\n\nReset: Use this to permanently reset the current intercom since resettting the script will not unlink it from the network.\n\nOn/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 chan,string name,key id,string mes) {
        if(chan != 0) jump menu_commands;
        if(!intercomON) return;
        if(!llGetListLength(urlBeacons)) return;
        integer idx = llListFindList(urlBeacons, (list)selfURL);
        list intercoms = llDeleteSubList(urlBeacons, idx, idx);
        if(!llGetListLength(intercoms)) return;
        do {
            string intercom = llList2String(intercoms, 0);
            if(!isValidURL(intercom)) jump next_intercom;
            requestQueue += llList2CSV([intercom, "PUT", name + ": " + mes]);
@next_intercom;
            intercoms = llDeleteSubList(intercoms, 0, 0);
        } while(llGetListLength(intercoms) != 0);
        return;
@menu_commands;
        if(mes != "[ On ]") jump menu_off;
        intercomON = TRUE;
        llInstantMessage(id, "Intercom is now: ON");
        jump close_chans;
@menu_off;
        if(mes != "[ Off ]") jump menu_reset;
        intercomON = FALSE;
        llInstantMessage(id, "Intercom is now: OFF");
        jump close_chans;
@menu_reset;
        if(mes != "[ Reset ]") jump menu_add_url;
        llMessageLinked(LINK_THIS, 0, "", "@wipe_storage");
        llResetScript();
        return;
@menu_add_url;
        if(mes != "[ Add URL ]") jump menu_my_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;
@menu_my_url;
        if(mes != "[ My URL ]") jump menu_list_urls;
        if(selfURL == "") {
            llInstantMessage(id, "I don't have an URL registered yet.");
            jump close_chans;
        }
        llInstantMessage(id, "My URL is: " + selfURL);
        jump close_chans;
@menu_list_urls;
        if(mes != "[ List URLs ]") jump register_urls;
        if(!llGetListLength(urlBeacons)) {
            llInstantMessage(id, "No beacons registered.");
            jump close_chans;
        }
        llInstantMessage(id, "----- INTERCOMS -----");
        integer itra=llGetListLength(urlBeacons)-1;
        do {
            llInstantMessage(id, llList2String(urlBeacons, itra));
        } while(--itra>-1);
        llInstantMessage(id, "----- INTERCOMS -----");
        jump close_chans;
@register_urls;
        if(chan != 89) return;
        mes = llList2String(llParseString2List(mes, [" "], [""]), 0);
        if(!isValidURL(mes)) {
            llInstantMessage(id, "Badly formatted URL");
            return;
        }
        if(llListFindList(urlBeacons, (list)mes) != -1) {
            llInstantMessage(id, "URL already exists.");
            jump close_chans;
        }
        urlBeacons += mes;
        llInstantMessage(id, "URL: " + mes + " has been registered.");
@close_chans;
        llListenRemove(registerHandle);
@close_menu;
        llListenRemove(menuHandle);
 
    }
 
    no_sensor() {
        list request = llCSV2List(llList2String(requestQueue, 0));
        string url = llList2String(request, 0);
        if(!isValidURL(url)) jump skip_request;
        llHTTPRequest(url, [HTTP_METHOD, llList2String(request, 1)], llList2String(request, 2));
@skip_request;
        requestQueue = llDeleteSubList(requestQueue, 0, 0);
    }
 
    timer() {
        // if we have no beacons or we are the only ones in the beacon list don't do anything
        if(llGetListLength(urlBeacons) == 0 || (llGetListLength(urlBeacons) == 1 && llList2String(urlBeacons, 0) == selfURL)) return;
        // if we still have pending synchronization requests, don't do anything
        integer itra = llGetListLength(requestQueue)-1;
        do {
            list requests = llCSV2List(llList2String(requestQueue, itra));
            if(llList2String(requests, 1) == "POST") return;
        } while(--itra>-1);
        // enqueue synchronization requests.
        itra = llGetListLength(urlBeacons)-1;
        do {
            string firstBeacon = llList2String(urlBeacons, itra);
            integer itrb = llGetListLength(urlBeacons)-1;
            do {
                if(itrb == itra) jump skip_beacon;
                string secondBeacon = llList2String(urlBeacons, itrb);
                requestQueue += llList2CSV([firstBeacon, "POST", secondBeacon + " " + INTERCOM_PASSWORD]);
@skip_beacon;
            } while(--itrb>-1);
        } while(--itra>-1);
    }
 
    http_request(key id, string method, string body) {
        if(method != URL_REQUEST_GRANTED) jump method_url_request_denied;
        selfURL = body;
        urlBeacons += body;
        return;
@method_url_request_denied;
        if(method != URL_REQUEST_DENIED) jump method_post;
        llRequestURL();
        return;
@method_post;
        if(method != "POST") jump method_put;
        list postPayload = llParseString2List(body, [" "], [""]);
        string firstPayload = llList2String(postPayload, 0);
        string secondPayload = llList2String(postPayload, 1);
        if(!isValidURL(firstPayload)) jump sync_error;
        if(secondPayload != INTERCOM_PASSWORD) jump sync_error;
        if(llListFindList(urlBeacons, (list)firstPayload) != -1) jump sync_error;
        urlBeacons += firstPayload;
        llHTTPResponse(id, 200, "SYNCED");
        return;
@sync_error;
        llHTTPResponse(id, 200, "ERROR");
        return;
@method_put;
        if(method != "PUT") jump say_error;
        if(!intercomON) jump say_error;
        llHTTPResponse(id, 200, "SAID");
        list sayPayload = llParseString2List(body, [": "], [""]);
        llSetObjectName(llList2String(sayPayload, 0));
        llSay(0, llList2String(sayPayload, 1));
        return;
@say_error;
        llHTTPResponse(id, 200, "ERROR");
        return;
    }
 
    http_response(key id, integer status, list metadata, string body) {
        if(status != 404) return;
        // if we got a bad beacon
        string badBeacon = llList2String(llParseString2List(body, [": ", "'"], [""]), 1);
        // remove the bad beacon from the beacon list and queues.
        urlBeacons = wasDeleteSubListMatch(urlBeacons, badBeacon);
    }
 
    changed(integer change) {
        if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_INVENTORY))
            llResetScript();
    }
 
    on_rez(integer pin) {
        llResetScript();
    }
}

secondlife/intercom.txt ยท Last modified: 2022/11/24 07:46 by 127.0.0.1

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.