LSL Protocol/Restrained Living Relay/Other Implementations/Maike Short's Relay
You can get a free relay object at the MSM Restraints shop in "Stonehaven". http://slurl.com/secondlife/Stonehaven%20Island/155/79/301
Changes
1.040
- added support for !who which allows world objects to tell who controls them
- added support for !handover to support inter-sim kidnappers leading directly into a trap and processing facilities that hand over the victim from one step to the next
- added support for !vision script from the Think Kink PBA by Ilana Debevec and Chloe1982 Constantine)
1.030.c
- Diff
- fixed group check on parcels not set to a group (which matched objects set to (none), too (reported by Kitty Barnett)
- fixed <0, 0, 0> pseudo object position in distance check (reported by Kitty Barnett).
1.030.b
- Diff
- fixed two faked avatar chat problems
1.030.a
- Diff
- Fixed the compatibility code for world objects that send @clear instead of !release broken by the security fix in 1.020.b
1.030
- Diff
- added "Temp Mute" in the ask dialog which will mute the object until the next time you login (in the extended controller script)
- tell world objects if an active session is canceled by the relay.
1.020.c
- Diff
- Display position and distance in permission dialog
1.020.b
- Diff
- This is based on 1.015 and has all the fixed made there missing in Marine's 1.020 which is based on a very early version of 1.014
- Filter automatic RLV replies on public chat channel 0 so that people cannot be tricked to say some foreign text out loud
- Added "this-is-a-script-generated-message-beyond-the-control-of-the-agent/" at the beginning of @getstatus-replies on all channel
- Note: Relays which do not add any restriction on their own (like @detach=n) may be abused using @gestatus to trigger dialog responses, gag talk or other scripts like "to buy as gift, say the name of the receiver on channel /x". Any version of the reference implementation smaller than (not including) 1.015 (but including 1.020 which is based on 1.014) are affected by this.
1.015
Code
<lsl> // RestrainedLife Viewer Relay Script // // By Maike Short // // While this script used to be based on Marine's sample implementations it was // so heavily modified and extended that there is little of the original code // remaining. So please do not bother Marine with any problems but report them to // https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features // // Thanks to Felis Darwin, Chorazin Allen, Azoth Amat, Gregor Mougin, // Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow, // Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others. // // Many thanks to she who started it all ... Marine Kelley. // // This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness // or performance. It may be distributed in its full source code with this header and // disclaimer and is not to be sold without permission. Optionally it may be distributed // in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source // IS provided (ie. this page or a .zip or .rar) within the object the code is contained // AND/OR a off-world contact point (ie. email) that the source code may be requested from. // This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and // reliability of the creator reference of in-world objects (scripts). Changes and // improvement to this code must be shared with the community so that we ALL benefit.
integer DEBUG = FALSE;
// list of refused commands. Use [] to allow everything or
// ["@detach", "@remoutfit"] to prevent striping for example
list refusedForceCommands = [];
// --------------------------------------------------- // Constants // ---------------------------------------------------
integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040"; // version of the implementation for debugging
string PREFIX_RL_COMMAND = "@"; string PREFIX_METACOMMAND = "!";
integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers integer DIALOG_CHANNEL = -1812220409; // RLVDI in numbers integer STATUS_LINK_CHANNEL = -1373421300; integer RLV_LINK_CHANNEL = -1373421301; integer CMD_LINK_CHANNEL = -1373421302; integer DIALOG_LINK_CHANNEL = -1373421303; integer ASK_LINK_CHANNEL = -1373421304; integer GPU_CHANNEL = -4360493;
integer MAX_OBJECT_DISTANCE = 100; // 100m is llShout distance
integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds
integer PERMISSION_DIALOG_TIMEOUT = 30; integer LOGIN_DELAY_WAIT_FOR_PONG = 20; integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;
integer MODE_OFF = 0; integer MODE_ASK = 1; integer MODE_AUTO = 2;
list MODE_DESCRIPTIONS = ["RLV Relay is OFF", "RLV Relay is ON (permission needed)", "RLV Relay is ON (auto-accept)"];
string RVL_COMMAND_START = "@this-is-a-script-generated-message-beyond-the-control-of-the-agent=n,detach=n"; string RVL_COMMAND_END = "@detach=y";
// --------------------------------------------------- // Variables // ---------------------------------------------------
integer mode;
list restrictions; // restrictions currently applied (without the "=n" part) integer visionRestricted; key source; // UUID of the object I'm commanded by, always equal to NULL_KEY if restrictions is empty, always set if not
string pendingName; // name of initiator of pending request (first request of a session in ask mode) key pendingId; // UUID of initiator of pending request (first request of a session in ask mode) string pendingMessage; // message of pending request integer pendingTime;
// used on login integer timerTickCounter; // count the number of time events on login (forceSit has to be delayed a bit) integer loginWaitingForPong; integer loginPendingForceSit;
key lastForceSitDestination; integer lastForceSitTime;
integer isNotEmbedded = TRUE;
// current message
// using global variables because the content can get huge and uses up a lot stack memory otherwise
string currentMessage = "";
list currentMessageToken = [];
string currentMessageCommands;
list currentMessageCommandList;
// --------------------------------------------------- // Helper functions // ---------------------------------------------------
debug(string x)
{
if (DEBUG) { llOwnerSay("DEBUG: " + x); }
}
// ---------------------------------------------------
// Low Level Communication
// ---------------------------------------------------
// acknowledge or reject
ack(string cmd_id, key id, string cmd, string ack)
{
llShout(RLVRS_CHANNEL, cmd_id + "," + (string) id + "," + cmd + "," + ack);
}
// cmd begins with a '@' sendRLCmd(string cmd) {
if (cmd != "") { if (isNotEmbedded) { llOwnerSay(cmd); } llMessageLinked(LINK_SET, RLV_LINK_CHANNEL, cmd, source); }
}
// splits the relay message into tokens splitMessage() {
currentMessageToken = llParseString2List(currentMessage, [","], []); currentMessageCommands = llList2String(currentMessageToken, 2); currentMessageCommandList = llParseString2List(currentMessageCommands, ["|"], []);
}
// check that this command is for us and not someone else integer verifyWeAreTarget() {
if (llGetListLength(currentMessageToken) != 3) // this is not a normal command { return FALSE; }
return (llList2String(currentMessageToken, 1) == llGetOwner()); // talking to me ?
}
// ---------------------------------------------------
// Permission Handling
// ---------------------------------------------------
// are we already under command by this object?
integer isObjectKnow(key id)
{
// are we already under command by this object? if (source == id) { return TRUE; }
// are we not under command by any object but were we forced to sit on this object recently? debug("source=" + (string) source + " id=" + (string) id + " lastForceSitDestination=" + (string) lastForceSitDestination); if ((source == NULL_KEY) && (id == lastForceSitDestination)) { debug("on last force sit target"); if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT > llGetUnixTime()) { debug("and recent enough to auto accept"); return TRUE; } }
return FALSE;
}
// check whether the object is in llShout distance.
integer isObjectNear(key id) { vector myPosition = llGetRootPosition(); list temp = llGetObjectDetails(id, ([OBJECT_POS])); vector objPosition = llList2Vector(temp,0); if (objPosition == <0, 0, 0>) { // object is not in this sim nor in one of the adjacent sims return FALSE; } float distance = llVecDist(objPosition, myPosition); return distance <= MAX_OBJECT_DISTANCE; }
// do a basic check on the identity of the object trying to issue a command integer isObjectIdentityTrustworthy(key id) {
key parcel_owner=llList2Key (llGetParcelDetails (llGetPos (), [PARCEL_DETAILS_OWNER]), 0); key parcel_group=llList2Key (llGetParcelDetails (llGetPos (), [PARCEL_DETAILS_GROUP]), 0); key object_owner=llGetOwnerKey(id); key object_group=llList2Key (llGetObjectDetails (id, [OBJECT_GROUP]), 0);
debug("owner= " + (string) parcel_owner + " / " + (string) object_owner); debug("group= " + (string) parcel_group + " / " + (string) object_group);
if (object_owner==llGetOwner () // IF I am the owner of the object || object_owner==parcel_owner // OR its owner is the same as the parcel I'm on || (object_group==parcel_group && parcel_group != NULL_KEY) // OR its group is the same as the parcel I'm on ) { return TRUE; } return FALSE;
}
// Is this a simple request for information or a meta command like !release?
integer isSimpleRequest()
{
integer len = llGetListLength(currentMessageCommandList); integer i; // now check every single atomic command for (i=0; i < len; ++i) { if (!isSimpleAtomicCommand(i)) { return FALSE; } }
// all atomic commands passed the test return TRUE;
}
// is this a simple atomar command // (a command which only queries some information or releases restrictions) // (e. g.: cmd ends with "=" and a number (@version, @getoutfit, @getattach) or is a !-meta-command) integer isSimpleAtomicCommand(integer i) {
string cmd = llList2String(currentMessageCommandList, i); cmd = llToLower(llStringTrim(cmd, STRING_TRIM)); // check right hand side of the "=" - sign integer index = llSubStringIndex (cmd, "="); if (index > -1) // there is a "=" { // check for a number after the "=" string param = llGetSubString (cmd, index + 1, -1); if ((integer)param!=0 || param=="0") // is it an integer (channel number)? { return TRUE; }
// removing restriction if ((param == "y") || (param == "rem")) { return TRUE; } }
// check for a leading ! (meta command) if (llSubStringIndex(cmd, PREFIX_METACOMMAND) == 0) { return llGetSubString(cmd, 0, 11) == "!visionclear" || llGetSubString(cmd, 0, 6) != "!vision"; }
// check for @clear // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login // (but we need this check here because "!release|@clear" is a BROKEN attempt to work around // a bug in the first relay implementation. You should refuse to use relay versions < 1013 // instead.) if (cmd == "@clear") { return TRUE; } // this one is not "simple". return FALSE;
}
// If we already have commands from this object pending // because of a permission request dialog, just add the // new commands at the end. // Note: We use a timeout here because the player may // have "ignored" the dialog. integer tryToGluePendingCommands(key id) {
if (llStringLength(pendingMessage) > 2000) { llSay(0, llKey2Name(id) + " is flooding commands. Releasing restrictions."); releaseRestrictions(); return TRUE; } if ((pendingId == id) && (pendingTime + PERMISSION_DIALOG_TIMEOUT > llGetUnixTime())) { debug("Gluing "); pendingMessage = pendingMessage + "|" + currentMessageCommands; return TRUE; } return FALSE;
}
// accept !release even if out of range
handleCommandsWhichAreAcceptedOutOfRange(key id)
{
if (id != source) { return; }
if (llGetListLength (currentMessageToken) < 3) { return; } if (llListFindList(currentMessageCommandList, ["!release"]) > -1) { debug("accepted !release although it was out of range"); releaseRestrictions(); }
}
// verifies the permission. This includes mode
// (off, permission, auto) of the relay and the
// identity of the object (owned by parcel people).
integer verifyPermission(key id, string name)
{
// is it switched off? if (mode == MODE_OFF) { return FALSE; }
if (llGetListLength (currentMessageToken) < 3) { return FALSE; }
// if we are already having a pending permission-dialog request for THIS object, // just add the new commands at the end of the pending command list. if (tryToGluePendingCommands(id)) { return FALSE; }
// accept harmless commands silently if (isSimpleRequest()) { return TRUE; }
// check whether this object belongs here integer trustworthy = isObjectIdentityTrustworthy(id); string warning = ""; if (!trustworthy) { warning = "\n\nWARNING: This object is not owned by the people owning this parcel. Unless you know the owner, you should deny this request."; }
// ask in permission-request-mode and/OR in case the object identity is suspisous. if (mode == MODE_ASK || !trustworthy) { debug("asking"); pendingId=id; pendingName=name;
currentMessageToken = []; currentMessageCommands = ""; currentMessageCommandList = [];
pendingMessage = currentMessage; currentMessage = "";
pendingTime = llGetUnixTime(); if (llKey2Name(llGetOwnerKey(id)) != "") { name += " (owned by " + llKey2Name(llGetOwnerKey(id)) + ")"; }
list temp = llGetObjectDetails(id, [OBJECT_POS]); vector pos = llList2Vector(temp, 0); string controller = getControllerName(); string text = name + " at <" + (string) ((integer) pos.x) + ", " + (string) ((integer) pos.y) + ", " + (string) ((integer) pos.z) + "> (" + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m)"; if (controller != "") { text = text + " operated by " + controller; } text = text + " would like to control your viewer." + warning + "\n\nDo you accept ?";
debug("Asking for permission isNotEmbedded=" + (string) isNotEmbedded); if (isNotEmbedded) { llDialog (llGetOwner(), text, ["Yes", "No"], DIALOG_CHANNEL); } llMessageLinked(LINK_SET, ASK_LINK_CHANNEL, text, id); debug("Asking for permission"); return FALSE; } return TRUE;
}
// gets the name of the last controller or "" // if there is either no !who-command, it is for NULL_KEY, // or the avatar is not in the sim string getControllerName() {
string controller = ""; integer i; integer count = llGetListLength(currentMessageCommandList); for (i = 0; i < count; i++) { string command = llList2String(currentMessageCommandList, i); list commandTokens = llParseString2List(command, ["/"], []); if (llList2String(commandTokens, 0) == "!who") { controller = llKey2Name(llList2String(commandTokens, 1)); } } return controller;
}
// --------------------------------------------------- // Executing of commands // ---------------------------------------------------
// execute a non-parsed message // this command could be denied here for policy reasons execute(string name, key id) {
currentMessage = ""; currentMessageCommands = ""; string cmd_id = llList2String(currentMessageToken, 0); // CheckAttach currentMessageToken = [];
integer len = llGetListLength (currentMessageCommandList); integer i; string command; string prefix; for (i=0; i<len; ++i) // execute every command one by one { // a command is a RL command if it starts with '@' or a metacommand if it starts with '!' command = llList2String(currentMessageCommandList, i); prefix = llGetSubString(command, 0, 0);
if (prefix == PREFIX_RL_COMMAND) // this is a RLV command { executeRLVCommand(cmd_id, id, command); } else if (prefix == PREFIX_METACOMMAND) // this is a metacommand, aimed at the relay itself { integer continue = executeMetaCommand(cmd_id, id, command); if (!continue) { return; } } }
}
// executes a command for the restrained life viewer // with some additinal magic like book keeping executeRLVCommand(string cmd_id, string id, string command) {
command = llToLower(command); // we need to know whether whether is a rule or a simple command list tokens = llParseString2List(command, ["="], []); string behav = llList2String(tokens, 0); // @getattach:skull string param = llList2String(tokens, 1); // 2222 integer ind = llListFindList(restrictions, [behav]);
tokens = llParseString2List(behav, [":"], []); // @sit:<uuid> string behavName = llList2String (tokens, 0); // @sit string option = llList2String (tokens, 1); // <uuid> debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!");
if (isQuery(behavName) && ((integer) param <= 0)) { // if this is a query command, don't accept public chat, // negative numbers, or pseudo channels like // "n", "add", "y", "rem", "force", ... ack(cmd_id, id, command, "ko"); return; }
if (param=="n" || param=="add") // add to restrictions { if (ind < 0) { if ((llGetListLength(restrictions) == 0) && !visionRestricted) { sendRLCmd(RVL_COMMAND_START); llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id); } restrictions += [behav]; } source = id; // we know that source is either NULL_KEY or id already } else if (param == "y" || param == "rem") // remove from restrictions { if (behavName == "@this-is-a-script-generated-message-beyond-the-control-of-the-agent") { ack(cmd_id, id, command, "ko"); return; } if (ind > -1) { restrictions = llDeleteSubList (restrictions, ind, ind); } if ((llGetListLength(restrictions) == 0) && !visionRestricted) { releaseRestrictions(); } removeFromPendingList(behav); } else if (param == "force") { if (llListFindList(refusedForceCommands, [behavName]) >= 0) { debug("rejected force-command: behav=!" + behav + "! behavName=!" + behavName + "!"); ack(cmd_id, id, command, "ko"); return; } } else if (((integer) param <= 0) && (behavName != "@clear")) { // this is either an unknown param (not "n", "add", "y", "rem", "force") // or a query which should be answered on the public chat channel 0. // note negative channels are displayed as /-x on public chat. ack(cmd_id, id, command, "ko"); return; }
workaroundForAtClear(command); rememberForceSit(behavName, option, param); sendRLCmd(command); // execute command ack(cmd_id, id, command, "ok"); // acknowledge
}
// is this a query? integer isQuery(string behav) {
return ((llGetSubString(behav, 0, 3) == "@get") || (llGetSubString(behav, 0, 7) == "@version") || (llGetSubString(behav, 0, 10) == "@findfolder"));
}
// check for @clear // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login // (but we need this check here because "!release|@clear" is a BROKEN attempt to work around // a bug in the first relay implementation. You should refuse to use relay versions < 1013 // instead.) workaroundForAtClear(string command) {
if (command == "@clear") { releaseRestrictions(); }
}
// remembers the time and object if this command is a force sit rememberForceSit(string behavName, string option, string param) {
// clear lastForceSitDestination in case we are now prevented from standing up and // the force sit was long ago. Note: restrictions is checked to prevent the // clearance in case @unsit is just send again on login if (behavName == "@unsit") { if (llListFindList(restrictions, ["@unsit"]) < 0) { if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT < llGetUnixTime()) { debug("clearing lastForceSitDestination"); lastForceSitDestination = NULL_KEY; } } } if (param != "force") { return; }
debug("'force'-command:" + behavName + "/" + option); if (behavName != "@sit") { return; } lastForceSitDestination = (key) option; lastForceSitTime = llGetUnixTime(); debug("remembered force sit");
}
// executes a meta command which is handled by the relay itself integer executeMetaCommand(string cmd_id, string id, string commandString) {
list tokens = llParseString2List(commandString, ["/"], []); string command = llList2String(tokens, 0); if (command == PREFIX_METACOMMAND + "version") // checking relay protocol version { ack(cmd_id, id, command, (string) RLVRS_PROTOCOL_VERSION); } else if (command == PREFIX_METACOMMAND + "implversion") // checking relay version { ack(cmd_id, id, command, RLVRS_IMPL_VERSION); } else if (command == PREFIX_METACOMMAND + "release") // release all the restrictions (end session) { releaseRestrictions(); ack(cmd_id, id, command, "ok"); } else if (command == PREFIX_METACOMMAND + "handover") { handleHandOver(id, tokens); return FALSE; } else if (command == PREFIX_METACOMMAND + "pong") // object is still available for us { loginWaitingForPong = FALSE; } else if (llGetSubString(command, 0, 11) == PREFIX_METACOMMAND + "visionclear") { visionRestricted = FALSE; ack(cmd_id, id, command, "ok"); if (llGetListLength(restrictions) == 0) { releaseRestrictions(); } llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source); } else if (llGetSubString(command, 0, 6) == PREFIX_METACOMMAND + "vision") { integer attached = llGetAttached(); if (attached >= ATTACH_HUD_CENTER_2 || attached < ATTACH_HUD_BOTTOM_RIGHT) { if (llGetListLength(restrictions) == 0) { llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id); sendRLCmd(RVL_COMMAND_START); } llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source); ack(cmd_id, id, command, "ok"); visionRestricted = TRUE; source = id; } else { ack(cmd_id, id, command, "ko"); } } else { ack(cmd_id, id, command, "ko"); } return TRUE;
}
handleHandOver(string id, list tokens)
{
debug("handover"); key targetObject = (key) llList2String(tokens, 1); integer keepRestrictions = (integer) llList2String(tokens, 2); if (!keepRestrictions) { releaseRestrictions(); } source = targetObject; if (llGetListLength(restrictions) == 0) { sendRLCmd(RVL_COMMAND_START); llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id); } pingWorldObjectIfUnderRestrictions();
}
// removes a restriction from the list of pending commands removeFromPendingList(string behav) {
string search = behav + "=";
// don't do the expensive parsing (string operations are very slow in pre- // mono LSL) in case we can detect fast that this one is not in the list. if (llSubStringIndex(pendingMessage, search) < 0) { return; }
list tokens = llParseString2List(pendingMessage, [","], []); list commands = llParseString2List(llList2String(tokens, 2), ["|"], []); integer modified = FALSE;
integer len = llGetListLength(commands); integer i; for (i = len - 1; i >= 0; i--) { string cmd = llList2String(commands, i); if (llSubStringIndex(cmd, search) == 0) { if (llSubStringIndex(cmd, "=n") > -1 || llSubStringIndex(cmd, "=add") > -1) { commands = llDeleteSubList(commands, i, i); modified = TRUE; } } }
if (modified) { if (llGetListLength(commands) > 0) { pendingMessage = llList2String(tokens, 0) + "," + llList2String(tokens, 1) + "," + llDumpList2String(commands, "|"); } else { clearPendingMessages(); } }
}
// lift all the restrictions (called by !release and by turning the relay off)
releaseRestrictions()
{
integer i; integer len = llGetListLength (restrictions); for (i = 0; i < len; ++i) { sendRLCmd(llList2String (restrictions, i) + "=y"); } sendRLCmd(RVL_COMMAND_END); lastForceSitDestination = NULL_KEY; restrictions = []; loginPendingForceSit = FALSE; clearPendingMessages(); visionRestricted = FALSE; llMessageLinked(LINK_SET, GPU_CHANNEL, "!visionclear", source); llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "release", source); source = NULL_KEY;
}
// sends an !release,ok to the world object if we are in an active session tellWorldObjectAboutCanceledSession() {
if (source != NULL_KEY) { ack("release", source, "!release", "ok"); }
}
// deletes the list of pending messsages clearPendingMessages() {
// clear pending request pendingName = ""; pendingId = NULL_KEY; pendingMessage = ""; pendingTime = 0;
}
// processes a message send on the relay channel
processRelayMessage(string name, key id)
{
if (mode== MODE_OFF) { debug("deactivated - ignoring commands"); return; // mode is 0 (off) => reject }
debug("LISTEN: " + currentMessage); splitMessage();
if (!verifyWeAreTarget()) { return; }
debug("Got message (active world object " + (string) source + "): name=" + name+ " id=" + (string) id); if (source) { if (source != id) { if (isObjectNear(source)) { debug("already used by another object => reject"); return; } else { debug("already used by another object, which is out of range"); releaseRestrictions(); } } }
if (!isObjectNear(id)) { handleCommandsWhichAreAcceptedOutOfRange(id); return; }
if (!isObjectKnow(id)) { debug("asking for permission because source is NULL_KEY"); if (!verifyPermission(id, name)) { return; } }
debug("Executing: " + (string) source); execute(name, id);
}
// --------------------------------------------------- // mode and dialog handling // ---------------------------------------------------
// shows current mode as string showModeDescription() {
if (isNotEmbedded) { llOwnerSay(llList2String(MODE_DESCRIPTIONS, mode)); } llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "show_mode", (key) ((string) mode));
}
// process the Yes/No buttons of the permission dialog
processDialogResponse(key id, string message)
{
if (pendingId) { if (message == "Yes") // pending request authorized => process it { debug("recovering pendingMessage"); currentMessage = pendingMessage; debug("splitting"); splitMessage(); debug("executing"); execute(pendingName, pendingId); } clearPendingMessages(); }
}
// --------------------------------------------------- // initialisation and login handling // ---------------------------------------------------
init() {
sendRLCmd("@clear"); llOwnerSay("Waiting for plugins to initialize..."); llSleep(5);
mode = MODE_ASK;
llListen(RLVRS_CHANNEL, "", "", ""); llListen(DIALOG_CHANNEL, "", llGetOwner(), "");
if (llGetFreeMemory() < 10000) { llSay(0, llKey2Name(llGetOwner()) + ", your relay is having very little free script memory and is likly to crash during use. Please verify that it was compiled using Mono."); } llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "init", (key) ((string) RLVRS_PROTOCOL_VERSION));
llOwnerSay("Initialized and ready"); debug("Free Memory: " + (string) llGetFreeMemory());
}
// sends the known restrictions (again) to the RL-viewer
// (call this functions on login)
reinforceKnownRestrictions()
{
integer i; integer len = llGetListLength(restrictions); string restr; debug("source=" + (string) source); if (len > 0) { sendRLCmd(RVL_COMMAND_START); } for (i=0; i < len; ++i) { restr = llList2String(restrictions, i); debug("restr=" + restr); sendRLCmd(restr + "=n"); if (restr == "@unsit") { loginPendingForceSit = TRUE; } }
}
// send a ping request and start a timer pingWorldObjectIfUnderRestrictions() {
loginWaitingForPong = FALSE; if (source) { ack("ping", source, "ping", "ping"); timerTickCounter = 0; llSetTimerEvent(1.0); loginWaitingForPong = TRUE; }
}
sendForceSitDuringLogin() {
key sitTarget = source; if (lastForceSitDestination) { sitTarget = lastForceSitDestination; } debug("Force sit during login on " + (string) sitTarget + " (source=" + (string) source + ", lastForceSitDestination=" + (string) lastForceSitDestination + ")"); sendRLCmd ("@sit:" + (string) sitTarget + "=force");
}
// processes a timer event
processTimer()
{
timerTickCounter++; debug("timer (" + (string) timerTickCounter + "): waiting for pong: " + (string) loginWaitingForPong + " pendingForceSit: " + (string) loginPendingForceSit); if (loginWaitingForPong && (timerTickCounter >= LOGIN_DELAY_WAIT_FOR_PONG)) { llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because the device is not available."); loginWaitingForPong = FALSE; loginPendingForceSit = FALSE; releaseRestrictions(); }
if (loginPendingForceSit) { integer agentInfo = llGetAgentInfo(llGetOwner()); if (agentInfo & AGENT_SITTING) { loginPendingForceSit = FALSE; debug("is sitting now"); } else if (timerTickCounter == LOGIN_DELAY_WAIT_FOR_FORCE_SIT) { llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because sitting down again was not possible."); loginPendingForceSit = FALSE; releaseRestrictions(); } else { sendForceSitDuringLogin(); } }
if (!loginPendingForceSit && !loginWaitingForPong) { llSetTimerEvent(0.0); }
}
processLinkMessage(key id, string message) {
if (message == "safeword") { llSay(0, llKey2Name(llGetOwner()) + " said the safeword. Freeing and deactivating relay."); tellWorldObjectAboutCanceledSession(); releaseRestrictions(); mode = MODE_OFF; showModeDescription(); return; }
if (message == "unlock") { if (id) { llSay(0, llKey2Name(id) + " freed " + llKey2Name(llGetOwner()) + " by unlocking the relay."); tellWorldObjectAboutCanceledSession(); } releaseRestrictions(); return; }
if (message == "mode") { if (source) { llOwnerSay("Sorry, you cannot change the relay mode while it is active."); return; } mode = ((integer) ((string) id)) % 3; showModeDescription(); return; }
if (message == "refusedForceCommands") { refusedForceCommands = llParseString2List((string) id, [","], []); return; }
if (message == "embedded") { isNotEmbedded = FALSE; return; }
}
default
{
state_entry() { init(); } on_rez(integer start_param) { // relogging, we must refresh the viewer and ping the object if any // if mode is not OFF, fire all the stored restrictions if (mode) { reinforceKnownRestrictions(); pingWorldObjectIfUnderRestrictions(); } // remind the current mode to the user showModeDescription(); } attach(key id) { if (id == NULL_KEY) { tellWorldObjectAboutCanceledSession(); releaseRestrictions(); } }
timer() { processTimer(); }
listen(integer channel, string name, key id, string message) { currentMessageToken = []; currentMessageCommands = ""; currentMessageCommandList = [];
currentMessage = message; message = ""; // free memory
if (channel==RLVRS_CHANNEL) { processRelayMessage(name, id); } else if (channel==DIALOG_CHANNEL) { processDialogResponse(id, message); } } link_message(integer sender, integer channel, string message, key id) { currentMessageToken = []; currentMessageCommands = ""; currentMessageCommandList = [];
if (channel == CMD_LINK_CHANNEL) { processLinkMessage(id, message); } else if (channel == DIALOG_LINK_CHANNEL) { processDialogResponse(id, message); } }
touch_start(integer num_detected) { if (!isNotEmbedded) { return; }
// touched by user => cycle through OFF/ON_PERMISSION/ON_ALWAYS modes key toucher = llDetectedKey(0); if (toucher != llGetOwner()) { return; }
if (source) { llOwnerSay("Sorry, you cannot change the relay mode while it is active."); return; } mode = (mode + 1) % 3; if (mode == MODE_OFF) { releaseRestrictions(); } showModeDescription(); }
changed(integer change) { if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP)) { llResetScript(); } if (change & (CHANGED_REGION | CHANGED_TELEPORT)) { if (loginWaitingForPong) { if (source) { ack("ping", source, "ping", "ping"); } } } }
} </lsl>