This is a zero lag, zero setup and self-linking local chat relay that listens on local chat to avatars talking and broadcasts the message to all other relays on the simulator. The script is intelligent enough to not collide with relays that are placed too close and to not relay messages in chat when the avatar talking is within range of the relay.
All that is needed to set up this local chat relay is to rez the object containing this script on the simulator where local chat will be relayed to. Intuitively, since local chat has a maximal range of , the relays should be placed more than apart in order to minimize the primitive usage. The relay can also be used to broadcast chat across large distances working thereby as an intercom.
/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2021 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueEncode(list data) { list k = llList2ListStrided(data, 0, -1, 2); list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); data = []; do { data += llList2String(k, 0) + "=" + llList2String(v, 0); k = llDeleteSubList(k, 0, 0); v = llDeleteSubList(v, 0, 0); } while(llGetListLength(k) != 0); return llDumpList2String(data, "&"); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueGet(string k, string data) { if(llStringLength(data) == 0) return ""; if(llStringLength(k) == 0) return ""; list a = llParseStringKeepNulls(data, ["&", "="], []); integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); if(i != -1) return llList2String(a, 2*i+1); return ""; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // escapes a string in conformance with RFC1738 string wasFormEncode(string i) { string o = ""; do { string c = llGetSubString(i, 0, 0); i = llDeleteSubString(i, 0, 0); if(c == "") jump continue; if(c == " ") { o += "+"; jump continue; } if(c == "\n") { o += "%0D" + llEscapeURL(c); jump continue; } o += llEscapeURL(c); @continue; } while(i != ""); return o; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // unescapes a string in conformance with RFC1738 string wasFormDecode(string i) { return llUnescapeURL( llDumpList2String( llParseString2List( llDumpList2String( llParseString2List( i, ["+"], [] ), " " ), ["%0D%0A"], [] ), "\n" ) ); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2021 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// float GetDistanceTo(key id) { return llVecDist( llList2Vector( llGetObjectDetails( id, [OBJECT_POS] ), 0 ), llGetPos() ); } list settings = []; integer relayChannel; integer adminChannel; list relays = []; default { state_entry() { // Set up the relay channel. relayChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF; // Listen to other relays. llListen(relayChannel, "", "", ""); // Listen to local chat. llListen(0, "", "", ""); } listen(integer channel, string name, key id, string text) { if(channel == relayChannel) { // Add the relay to the list of relays. if(llListFindList(relays, [id]) == -1) { relays += id; } // Avoid broadcasting message from relays placed too close. if(GetDistanceTo(id) < 20) { return; } key prim = (key)wasFormDecode( wasKeyValueGet( "prim", text ) ); // Skip broadcasting message if the avatar is too close. if(GetDistanceTo(prim) < 20) { return; } // Say the message on local chat. string agent = wasFormDecode( wasKeyValueGet( "name", text ) ); string message = wasFormDecode( wasKeyValueGet( "text", text ) ); llSay(0, agent + ": " + message); return; } if(channel == 0) { // Skip message from relays. if(llListFindList(relays, [id]) != -1) { return; } // Broadcast the message. string encode = wasKeyValueEncode( [ "name", wasFormEncode(name), "text", wasFormEncode(text), "prim", wasFormEncode(id) ] ); llRegionSay(relayChannel, encode); return; } } on_rez(integer num) { llResetScript(); } changed(integer change) { llResetScript(); } }