LSL Protocol/Restrained Living Relay/Other Implementations/Maike Short's Relay: Difference between revisions
Maike Short (talk | contribs) m change log |
Maike Short (talk | contribs) version 1.040 |
||
| Line 4: | Line 4: | ||
== Changes == | == 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 [[LSL_Protocol/Restrained_Life_Relay/Other_Implementations/The_Think_Kink_PBA#The_GPU|!vision script]] from the Think Kink PBA by Ilana Debevec and Chloe1982 Constantine) | |||
=== 1.030.c === | === 1.030.c === | ||
| Line 9: | Line 16: | ||
* fixed group check on parcels not set to a group (which matched objects set to (none), too (reported by Kitty Barnett) | * 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). | * fixed <0, 0, 0> pseudo object position in distance check (reported by Kitty Barnett). | ||
=== 1.030.b === | === 1.030.b === | ||
| Line 44: | Line 50: | ||
<lsl> | <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; | integer DEBUG = FALSE; | ||
| Line 68: | Line 85: | ||
// Constants | // Constants | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
integer RLVRS_PROTOCOL_VERSION = | integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page | ||
string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1. | string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040"; // version of the implementation for debugging | ||
string PREFIX_RL_COMMAND = "@"; | string PREFIX_RL_COMMAND = "@"; | ||
string PREFIX_METACOMMAND = "!"; | string PREFIX_METACOMMAND = "!"; | ||
integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers | integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers | ||
integer DIALOG_CHANNEL = -1812220409; // RLVDI in numbers | integer DIALOG_CHANNEL = -1812220409; // RLVDI in numbers | ||
integer STATUS_LINK_CHANNEL = -1373421300; | integer STATUS_LINK_CHANNEL = -1373421300; | ||
| Line 82: | Line 99: | ||
integer DIALOG_LINK_CHANNEL = -1373421303; | integer DIALOG_LINK_CHANNEL = -1373421303; | ||
integer ASK_LINK_CHANNEL = -1373421304; | integer ASK_LINK_CHANNEL = -1373421304; | ||
integer GPU_CHANNEL = -4360493; | |||
integer MAX_OBJECT_DISTANCE = 100; // 100m is llShout distance | integer MAX_OBJECT_DISTANCE = 100; // 100m is llShout distance | ||
| Line 87: | Line 106: | ||
integer PERMISSION_DIALOG_TIMEOUT = 30; | integer PERMISSION_DIALOG_TIMEOUT = 30; | ||
integer LOGIN_DELAY_WAIT_FOR_PONG = | integer LOGIN_DELAY_WAIT_FOR_PONG = 20; | ||
integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60; | integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60; | ||
| Line 102: | Line 121: | ||
// Variables | // Variables | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
integer mode; | integer mode; | ||
list restrictions; // restrictions currently applied (without the "=n" part) | 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 | 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 mode | 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 mode | key pendingId; // UUID of initiator of pending request (first request of a session in ask mode) | ||
string pendingMessage; // message of pending request | string pendingMessage; // message of pending request | ||
integer pendingTime; | integer pendingTime; | ||
| Line 122: | Line 142: | ||
integer isNotEmbedded = TRUE; | 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; | |||
// --------------------------------------------------- | // --------------------------------------------------- | ||
| Line 134: | Line 162: | ||
llOwnerSay("DEBUG: " + x); | llOwnerSay("DEBUG: " + x); | ||
} | } | ||
} | } | ||
| Line 150: | Line 169: | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
// acknowledge or reject | // acknowledge or reject | ||
ack(string cmd_id, key id, string cmd, string ack) | ack(string cmd_id, key id, string cmd, string ack) | ||
{ | { | ||
llShout(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack); | llShout(RLVRS_CHANNEL, cmd_id + "," + (string) id + "," + cmd + "," + ack); | ||
} | } | ||
// cmd begins with a '@' | // cmd begins with a '@' | ||
sendRLCmd(string cmd) | sendRLCmd(string cmd) | ||
{ | { | ||
| Line 170: | Line 189: | ||
} | } | ||
// 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 | // check that this command is for us and not someone else | ||
integer verifyWeAreTarget( | integer verifyWeAreTarget() | ||
{ | { | ||
if (llGetListLength(currentMessageToken) != 3) // this is not a normal command | |||
if (llGetListLength( | |||
{ | { | ||
return FALSE; | return FALSE; | ||
} | } | ||
return (llList2String( | return (llList2String(currentMessageToken, 1) == llGetOwner()); // talking to me ? | ||
} | } | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
| Line 209: | Line 224: | ||
// are we not under command by any object but were we forced to sit on this object recently? | // 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)) | if ((source == NULL_KEY) && (id == lastForceSitDestination)) | ||
{ | { | ||
| Line 224: | Line 240: | ||
// check whether the object is in llShout distance. | // check whether the object is in llShout distance. | ||
integer isObjectNear(key id) | 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 | // do a basic check on the identity of the object trying to issue a command | ||
| Line 249: | Line 265: | ||
debug("group= " + (string) parcel_group + " / " + (string) object_group); | debug("group= " + (string) parcel_group + " / " + (string) object_group); | ||
if (object_owner==llGetOwner() | 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_owner==parcel_owner // OR its owner is the same as the parcel I'm on | ||
|| (object_group==parcel_group && parcel_group != NULL_KEY) | || (object_group==parcel_group && parcel_group != NULL_KEY) // OR its group is the same as the parcel I'm on | ||
) | ) | ||
{ | { | ||
| Line 261: | Line 277: | ||
// Is this a simple request for information or a meta command like !release? | // Is this a simple request for information or a meta command like !release? | ||
integer isSimpleRequest( | integer isSimpleRequest() | ||
{ | { | ||
integer len = llGetListLength( | integer len = llGetListLength(currentMessageCommandList); | ||
integer i; | integer i; | ||
// now check every single atomic command | // now check every single atomic command | ||
for (i=0; i < len; ++i) | for (i=0; i < len; ++i) | ||
{ | { | ||
if (!isSimpleAtomicCommand(i)) | |||
if (!isSimpleAtomicCommand( | |||
{ | { | ||
return FALSE; | return FALSE; | ||
| Line 283: | Line 297: | ||
// (a command which only queries some information or releases restrictions) | // (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) | // (e. g.: cmd ends with "=" and a number (@version, @getoutfit, @getattach) or is a !-meta-command) | ||
integer isSimpleAtomicCommand( | integer isSimpleAtomicCommand(integer i) | ||
{ | { | ||
string cmd = llList2String(currentMessageCommandList, i); | |||
cmd = llToLower(llStringTrim(cmd, STRING_TRIM)); | |||
// check right hand side of the "=" - sign | // check right hand side of the "=" - sign | ||
integer index = llSubStringIndex (cmd, "="); | integer index = llSubStringIndex (cmd, "="); | ||
if (index > -1) // there is a "=" | if (index > -1) // there is a "=" | ||
{ | { | ||
// check for a number after the "=" | // check for a number after the "=" | ||
| Line 306: | Line 322: | ||
if (llSubStringIndex(cmd, PREFIX_METACOMMAND) == 0) | if (llSubStringIndex(cmd, PREFIX_METACOMMAND) == 0) | ||
{ | { | ||
return | return llGetSubString(cmd, 0, 11) == "!visionclear" | ||
|| llGetSubString(cmd, 0, 6) != "!vision"; | |||
} | } | ||
| Line 318: | Line 335: | ||
return TRUE; | return TRUE; | ||
} | } | ||
// this one is not "simple". | // this one is not "simple". | ||
return FALSE; | return FALSE; | ||
| Line 328: | Line 345: | ||
// Note: We use a timeout here because the player may | // Note: We use a timeout here because the player may | ||
// have "ignored" the dialog. | // have "ignored" the dialog. | ||
integer tryToGluePendingCommands(key id | integer tryToGluePendingCommands(key id) | ||
{ | { | ||
if (llStringLength(pendingMessage) > | if (llStringLength(pendingMessage) > 2000) | ||
{ | { | ||
llSay(0, llKey2Name(id) + " is flooding commands. Releasing restrictions."); | llSay(0, llKey2Name(id) + " is flooding commands. Releasing restrictions."); | ||
| Line 338: | Line 355: | ||
if ((pendingId == id) && (pendingTime + PERMISSION_DIALOG_TIMEOUT > llGetUnixTime())) | if ((pendingId == id) && (pendingTime + PERMISSION_DIALOG_TIMEOUT > llGetUnixTime())) | ||
{ | { | ||
debug("Gluing " | debug("Gluing "); | ||
pendingMessage = pendingMessage + "|" + | pendingMessage = pendingMessage + "|" + currentMessageCommands; | ||
return TRUE; | return TRUE; | ||
} | } | ||
| Line 347: | Line 364: | ||
// accept !release even if out of range | // accept !release even if out of range | ||
handleCommandsWhichAreAcceptedOutOfRange(key id | handleCommandsWhichAreAcceptedOutOfRange(key id) | ||
{ | { | ||
if (id != source) | if (id != source) | ||
| Line 354: | Line 371: | ||
} | } | ||
if (llGetListLength (currentMessageToken) < 3) | |||
if (llGetListLength ( | |||
{ | { | ||
return; | return; | ||
} | } | ||
if (llListFindList(currentMessageCommandList, ["!release"]) > -1) | |||
if (llListFindList( | |||
{ | { | ||
debug("accepted !release although it was out of range"); | debug("accepted !release although it was out of range"); | ||
| Line 369: | Line 383: | ||
// verifies the permission. This includes mode | // verifies the permission. This includes mode | ||
// (off, permission, auto) of the relay and the | // (off, permission, auto) of the relay and the | ||
// identity of the object (owned by parcel people). | // identity of the object (owned by parcel people). | ||
integer verifyPermission(key id, string name | integer verifyPermission(key id, string name) | ||
{ | { | ||
// is it switched off? | // is it switched off? | ||
| Line 380: | Line 394: | ||
} | } | ||
if (llGetListLength (currentMessageToken) < 3) | |||
if (llGetListLength ( | |||
{ | { | ||
return FALSE; | return FALSE; | ||
} | } | ||
// if we are already having a pending permission-dialog request for THIS object, | // 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. | // just add the new commands at the end of the pending command list. | ||
if (tryToGluePendingCommands(id | if (tryToGluePendingCommands(id)) | ||
{ | { | ||
return FALSE; | return FALSE; | ||
| Line 396: | Line 407: | ||
// accept harmless commands silently | // accept harmless commands silently | ||
if (isSimpleRequest( | if (isSimpleRequest()) | ||
{ | { | ||
return TRUE; | return TRUE; | ||
| Line 412: | Line 423: | ||
if (mode == MODE_ASK || !trustworthy) | if (mode == MODE_ASK || !trustworthy) | ||
{ | { | ||
debug("asking"); | |||
pendingId=id; | pendingId=id; | ||
pendingName=name; | pendingName=name; | ||
pendingMessage = | |||
currentMessageToken = []; | |||
currentMessageCommands = ""; | |||
currentMessageCommandList = []; | |||
pendingMessage = currentMessage; | |||
currentMessage = ""; | |||
pendingTime = llGetUnixTime(); | pendingTime = llGetUnixTime(); | ||
if (llKey2Name(llGetOwnerKey(id)) != "") | if (llKey2Name(llGetOwnerKey(id)) != "") | ||
| Line 423: | Line 442: | ||
list temp = llGetObjectDetails(id, [OBJECT_POS]); | list temp = llGetObjectDetails(id, [OBJECT_POS]); | ||
vector pos = llList2Vector(temp, 0); | vector pos = llList2Vector(temp, 0); | ||
string controller = getControllerName(); | |||
string text = name | |||
string text = name | |||
+ " at <" + (string) ((integer) pos.x) | + " at <" + (string) ((integer) pos.x) | ||
+ ", " + (string) ((integer) pos.y) | + ", " + (string) ((integer) pos.y) | ||
+ ", " + (string) ((integer) pos.z) | + ", " + (string) ((integer) pos.z) | ||
+ "> (" + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m) " | + "> (" + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m)"; | ||
+ "would like to control your viewer." + warning + " | 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); | debug("Asking for permission isNotEmbedded=" + (string) isNotEmbedded); | ||
if (isNotEmbedded) | if (isNotEmbedded) | ||
| Line 442: | Line 467: | ||
} | } | ||
// 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; | |||
} | |||
// --------------------------------------------------- | // --------------------------------------------------- | ||
| Line 448: | Line 492: | ||
// execute a non-parsed message | // execute a non-parsed message | ||
// this command could be denied here for policy reasons | // this command could be denied here for policy reasons | ||
execute(string name, key id) | |||
execute(string name, key id | |||
{ | { | ||
currentMessage = ""; | |||
string cmd_id = llList2String( | currentMessageCommands = ""; | ||
string cmd_id = llList2String(currentMessageToken, 0); // CheckAttach | |||
currentMessageToken = []; | |||
integer len = llGetListLength ( | integer len = llGetListLength (currentMessageCommandList); | ||
integer i; | integer i; | ||
string command; | string command; | ||
| Line 463: | Line 507: | ||
{ | { | ||
// a command is a RL command if it starts with '@' or a metacommand if it starts with '!' | // a command is a RL command if it starts with '@' or a metacommand if it starts with '!' | ||
command = llList2String( | command = llList2String(currentMessageCommandList, i); | ||
prefix = llGetSubString(command, 0, 0); | prefix = llGetSubString(command, 0, 0); | ||
| Line 472: | Line 516: | ||
else if (prefix == PREFIX_METACOMMAND) // this is a metacommand, aimed at the relay itself | else if (prefix == PREFIX_METACOMMAND) // this is a metacommand, aimed at the relay itself | ||
{ | { | ||
executeMetaCommand(cmd_id, id, command); | integer continue = executeMetaCommand(cmd_id, id, command); | ||
if (!continue) | |||
{ | |||
return; | |||
} | |||
} | } | ||
} | } | ||
} | } | ||
// executes a command for the restrained life viewer | // executes a command for the restrained life viewer | ||
// with some additinal magic like book keeping | // with some additinal magic like book keeping | ||
executeRLVCommand(string cmd_id, string id, string command) | executeRLVCommand(string cmd_id, string id, string command) | ||
| Line 491: | Line 539: | ||
string behavName = llList2String (tokens, 0); // @sit | string behavName = llList2String (tokens, 0); // @sit | ||
string option = llList2String (tokens, 1); // <uuid> | string option = llList2String (tokens, 1); // <uuid> | ||
debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!"); | debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!"); | ||
| Line 507: | Line 555: | ||
if (ind < 0) | if (ind < 0) | ||
{ | { | ||
if (llGetListLength(restrictions) == 0) | if ((llGetListLength(restrictions) == 0) && !visionRestricted) | ||
{ | { | ||
sendRLCmd(RVL_COMMAND_START); | sendRLCmd(RVL_COMMAND_START); | ||
llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id); | llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id); | ||
} | } | ||
restrictions += [behav]; | restrictions += [behav]; | ||
} | } | ||
| Line 523: | Line 571: | ||
return; | return; | ||
} | } | ||
if (ind > -1) | if (ind > -1) | ||
{ | { | ||
restrictions = llDeleteSubList (restrictions, ind, ind); | restrictions = llDeleteSubList (restrictions, ind, ind); | ||
} | } | ||
if (llGetListLength(restrictions) == 0) | if ((llGetListLength(restrictions) == 0) && !visionRestricted) | ||
{ | { | ||
releaseRestrictions(); | |||
} | } | ||
removeFromPendingList(behav); | removeFromPendingList(behav); | ||
| Line 562: | Line 609: | ||
integer isQuery(string behav) | integer isQuery(string behav) | ||
{ | { | ||
return ((llGetSubString(behav, 0, 3) == "@get") || | |||
(llGetSubString(behav, 0, 7) == "@version") || | |||
(llGetSubString(behav, 0, 10) == "@findfolder")); | |||
} | } | ||
| Line 607: | Line 644: | ||
} | } | ||
} | } | ||
if (param != "force") | if (param != "force") | ||
{ | { | ||
| Line 624: | Line 661: | ||
// executes a meta command which is handled by the relay itself | // executes a meta command which is handled by the relay itself | ||
executeMetaCommand(string cmd_id, string id, string | 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 | if (command == PREFIX_METACOMMAND + "version") // checking relay protocol version | ||
{ | { | ||
| Line 639: | Line 679: | ||
ack(cmd_id, id, command, "ok"); | ack(cmd_id, id, command, "ok"); | ||
} | } | ||
else if (command == PREFIX_METACOMMAND + "pong") | 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; | 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(); | |||
} | } | ||
| Line 650: | Line 749: | ||
string search = behav + "="; | string search = behav + "="; | ||
// don't do the expensive parsing (string operations are very slow in pre- | // 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. | // mono LSL) in case we can detect fast that this one is not in the list. | ||
if (llSubStringIndex(pendingMessage, search) < 0) | if (llSubStringIndex(pendingMessage, search) < 0) | ||
| Line 672: | Line 771: | ||
commands = llDeleteSubList(commands, i, i); | commands = llDeleteSubList(commands, i, i); | ||
modified = TRUE; | modified = TRUE; | ||
} | } | ||
} | } | ||
} | } | ||
| Line 690: | Line 789: | ||
} | } | ||
// lift all the restrictions (called by !release and by turning the relay off) | // lift all the restrictions (called by !release and by turning the relay off) | ||
releaseRestrictions() | releaseRestrictions() | ||
| Line 705: | Line 804: | ||
loginPendingForceSit = FALSE; | loginPendingForceSit = FALSE; | ||
clearPendingMessages(); | clearPendingMessages(); | ||
visionRestricted = FALSE; | |||
llMessageLinked(LINK_SET, GPU_CHANNEL, "!visionclear", source); | |||
llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "release", source); | llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "release", source); | ||
source = NULL_KEY; | source = NULL_KEY; | ||
| Line 732: | Line 833: | ||
// processes a message send on the relay channel | // processes a message send on the relay channel | ||
processRelayMessage(string name, key id | processRelayMessage(string name, key id) | ||
{ | { | ||
if (mode== MODE_OFF) | if (mode== MODE_OFF) | ||
| Line 740: | Line 841: | ||
} | } | ||
debug("LISTEN: " + | debug("LISTEN: " + currentMessage); | ||
splitMessage(); | |||
if (!verifyWeAreTarget( | if (!verifyWeAreTarget()) | ||
{ | { | ||
return; | return; | ||
} | } | ||
debug("Got message (active world object " + (string) source + "): name=" + name+ " id=" + (string) id); | |||
debug("Got message (active world object " + (string) source + "): name=" + name+ " id=" + (string) id | |||
if (source) | if (source) | ||
{ | { | ||
| Line 771: | Line 868: | ||
} | } | ||
if (!isObjectNear(id)) | if (!isObjectNear(id)) | ||
{ | { | ||
handleCommandsWhichAreAcceptedOutOfRange(id | handleCommandsWhichAreAcceptedOutOfRange(id); | ||
return; | return; | ||
} | } | ||
| Line 780: | Line 877: | ||
{ | { | ||
debug("asking for permission because source is NULL_KEY"); | debug("asking for permission because source is NULL_KEY"); | ||
if (!verifyPermission(id, name | if (!verifyPermission(id, name)) | ||
{ | { | ||
return; | return; | ||
| Line 787: | Line 884: | ||
debug("Executing: " + (string) source); | debug("Executing: " + (string) source); | ||
execute(name, id | execute(name, id); | ||
} | } | ||
| Line 814: | Line 911: | ||
if (message == "Yes") // pending request authorized => process it | if (message == "Yes") // pending request authorized => process it | ||
{ | { | ||
execute(pendingName, pendingId | debug("recovering pendingMessage"); | ||
currentMessage = pendingMessage; | |||
debug("splitting"); | |||
splitMessage(); | |||
debug("executing"); | |||
execute(pendingName, pendingId); | |||
} | } | ||
clearPendingMessages(); | clearPendingMessages(); | ||
| Line 821: | Line 923: | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
// initialisation and login handling | // initialisation and login handling | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
init() { | init() { | ||
sendRLCmd("@clear"); | sendRLCmd("@clear"); | ||
| Line 965: | Line 1,067: | ||
return; | return; | ||
} | } | ||
mode = ((integer) ((string) id)) % 3; | mode = ((integer) ((string) id)) % 3; | ||
showModeDescription(); | showModeDescription(); | ||
return; | return; | ||
| Line 990: | Line 1,092: | ||
init(); | init(); | ||
} | } | ||
on_rez(integer start_param) | on_rez(integer start_param) | ||
{ | { | ||
| Line 1,003: | Line 1,105: | ||
showModeDescription(); | showModeDescription(); | ||
} | } | ||
attach(key id) | attach(key id) | ||
{ | { | ||
| Line 1,020: | Line 1,122: | ||
listen(integer channel, string name, key id, string message) | listen(integer channel, string name, key id, string message) | ||
{ | { | ||
currentMessageToken = []; | |||
currentMessageCommands = ""; | |||
currentMessageCommandList = []; | |||
currentMessage = message; | |||
message = ""; // free memory | |||
if (channel==RLVRS_CHANNEL) | if (channel==RLVRS_CHANNEL) | ||
{ | { | ||
processRelayMessage(name, id | processRelayMessage(name, id); | ||
} | } | ||
else if (channel==DIALOG_CHANNEL) | else if (channel==DIALOG_CHANNEL) | ||
| Line 1,029: | Line 1,138: | ||
} | } | ||
} | } | ||
link_message(integer sender, integer channel, string message, key id) | link_message(integer sender, integer channel, string message, key id) | ||
{ | { | ||
currentMessageToken = []; | |||
currentMessageCommands = ""; | |||
currentMessageCommandList = []; | |||
if (channel == CMD_LINK_CHANNEL) | if (channel == CMD_LINK_CHANNEL) | ||
{ | { | ||
| Line 1,062: | Line 1,175: | ||
} | } | ||
mode = (mode + 1) % 3; | mode = (mode + 1) % 3; | ||
if (mode == MODE_OFF) { | if (mode == MODE_OFF) | ||
{ | |||
releaseRestrictions(); | releaseRestrictions(); | ||
} | } | ||
| Line 1,070: | Line 1,184: | ||
changed(integer change) | changed(integer change) | ||
{ | { | ||
if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP)) | if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP)) | ||
{ | { | ||
llResetScript(); | llResetScript(); | ||
} | |||
if (change & (CHANGED_REGION | CHANGED_TELEPORT)) | |||
{ | |||
if (loginWaitingForPong) | |||
{ | |||
if (source) | |||
{ | |||
ack("ping", source, "ping", "ping"); | |||
} | |||
} | |||
} | } | ||
} | } | ||
} | } | ||
</lsl> | </lsl> | ||
Revision as of 09:21, 6 March 2009
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>