13 November 2012
12 November 2012
Becky Pippen's
compression since it was redundant and caused LSL implementation-specific problems.26 August 2011
touch()
to touch_start()
.14 August 2011
Becky Pippen
so that we can store up to 6 URLs in pseudo-permanent storage.13 August 2011
12 August 2011
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.
A
and one on simulator B
.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.
A
and on simulator B
.Now you have two options:
[ My URL ]
and then touching the other intercom, choosing [ Add URL ]
and add the URL from the first intercom.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.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.
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.
Here are some examples of what this script will be able to do for you:
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.
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.
/////////////////////////////////////////////////////////////////////////// // 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(); } }