|
|
(14 intermediate revisions by 6 users not shown) |
Line 1: |
Line 1: |
| {{Restrained Life Relay Specs TOC}} | | {{Restrained Life Relay Specs TOC}} |
|
| |
|
| You can get a free relay object at the MSM Restraints shop in "Stonehaven". http://slurl.com/secondlife/Stonehaven%20Island/155/79/301
| | __NOTOC__ |
| | |
| | |
| | |
| | == Introduction == |
| | |
| | Note: It is likely this relay implementation and this present page won't be updated anymore, as Maike Short's accounts have been banned (for some reasons you can easily find out and are not the topic here anyway). The code has been evolved by other people, and many in-world relays have been using it for months now: the relay developing community is more alive than ever. And there are new relays out there developed by smart people from scratch. Thank you Maike, for having made all of this possible! |
| | |
| | |
| | == Code == |
| | |
| | <div style="border: 3px solid red; padding: 1em; background-color:#FFA"> |
| | '''Note: The code on this page was highly outdated and incompatible with the current version of the second life server.''' |
| | |
| | The assumptions that linked messages are a lot faster than said messages is not true anymore. It is not worth to fix the code because there are other free and open source implementations are out there that are way better by now. --Maike Short |
| | </div> |
| | |
| | |
|
| |
|
| == Changes == | | == Changes == |
| | |
| | === 1.040.e === |
| | |
| | * workaround for a security issue causing the permission request dialog to be skipped or displayed twice because of a multi threading vulnerability. This issue did not occur in old versions of the Second Life Server software because processing of listen events used to be very slow compared to linked messages. |
| | * [http://wiki.secondlife.com/w/index.php?title=LSL_Protocol%2FRestrained_Life_Relay%2FOther_Implementations%2FMaike_Short's_Relay%2FRelay_Manager&diff=422682&oldid=341282 my change] |
| | |
| | === 1.040.d === |
| | * added a filter option to ignore auto granting of permission. If you are sitting while under @acceptpermission a griefer who is not involved at all can get your animation permission while being several hundred meters away |
| | |
| | === 1.040.c === |
| | * fixed a !handover permission bug |
| | |
| | === 1.040.b === |
| | * added additional options to the filter (im, vision, windlight, avatar sex) |
| | * included permissionless tp in tp filter |
| | * included new rlv-commands in strip filter |
| | * clean up windlight settings on release if they have been messed with (does not reset daytime on every released, just when it is required) |
| | * some code cleanup |
| | |
| | === 1.040.a === |
| | * added support for multiple world objects |
| | * show world map with tp destination if force-tp is disabled |
|
| |
|
| === 1.040 === | | === 1.040 === |
Line 46: |
Line 85: |
|
| |
|
| * see [[LSL Protocol/Restrained Life Relay/Change_History#1.014_to_1.015.a|Changes to the reference implemenation 1.014 to 1.015.a]] | | * see [[LSL Protocol/Restrained Life Relay/Change_History#1.014_to_1.015.a|Changes to the reference implemenation 1.014 to 1.015.a]] |
|
| |
| == 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>
| |