Difference between revisions of "LSL Protocol/Restrained Love Relay/Other Implementations/Felis Darwin's Amethyst Plugin"
Felis Darwin (talk | contribs) |
Felis Darwin (talk | contribs) |
||
Line 6: | Line 6: | ||
//== by Felis Darwin | //== by Felis Darwin | ||
//== Based on Reference Implementation by Marine Kelley | //== Based on Reference Implementation by Marine Kelley | ||
integer ALLOW_REMOVE_DETACH = TRUE; | |||
integer DEBUG = FALSE; | |||
// | // --------------------------------------------------- | ||
// | // Amethyst Plugin Variables | ||
// | // --------------------------------------------------- | ||
key nullkey = NULL_KEY; | |||
string nullstr = ""; | |||
// | integer secaccess=0; //== Do secondary owners have access to the RL functions? | ||
// Internal variables | |||
key ownerkey = nullkey; | |||
// | list secowners = []; | ||
key setby = NULL_KEY; //== Who set the RLV Relay status? | |||
// | |||
integer lockstatus; //== Has the collar been locked by the RLV plugin? | |||
// --------------------------------------------------- | // --------------------------------------------------- | ||
Line 39: | Line 35: | ||
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 MAX_OBJECT_DISTANCE = 20; // 20m is llSay distance | integer MAX_OBJECT_DISTANCE = 20; // 20m is llSay distance | ||
integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = | integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds | ||
integer PERMISSION_DIALOG_TIMEOUT = 30; | integer PERMISSION_DIALOG_TIMEOUT = 30; | ||
integer LOGIN_DELAY_WAIT_FOR_PONG = 10; | integer LOGIN_DELAY_WAIT_FOR_PONG = 10; | ||
integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60; | integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60; | ||
integer PING_INTERVAL = 60; //== Time between pings | |||
integer MODE_OFF = 0; | integer MODE_OFF = 0; | ||
integer MODE_ASK = 1; | integer MODE_ASK = 1; | ||
integer MODE_AUTO = 2; | integer MODE_AUTO = 2; | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
// Variables | // Variables | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
integer nMode; | |||
list lRestrictions; // restrictions currently applied (without the "=n" part) | list lRestrictions; // restrictions currently applied (without the "=n" part) | ||
key kSource; // UUID of the object I'm commanded by, always equal to NULL_KEY if lRestrictions is empty, always set if not | key kSource; // UUID of the object I'm commanded by, always equal to NULL_KEY if lRestrictions is empty, always set if not | ||
string sPendingName; // name of initiator of pending request (first request of a session in mode 1) | string sPendingName; // name of initiator of pending request (first request of a session in mode 1) | ||
key sPendingId; // UUID of initiator of pending request (first request of a session in mode 1) | key sPendingId; // UUID of initiator of pending request (first request of a session in mode 1) | ||
string sPendingMessage; // message of pending request (first request of a session in mode 1) | string sPendingMessage; // message of pending request (first request of a session in mode 1) | ||
integer sPendingTime; | integer sPendingTime; | ||
// used on login | // used on login | ||
integer timerTickCounter; // count the number of time events on login (forceSit has to be delayed a bit) | integer timerTickCounter; // count the number of time events on login (forceSit has to be delayed a bit) | ||
integer loginWaitingForPong; | integer loginWaitingForPong; | ||
integer loginPendingForceSit; | integer loginPendingForceSit; | ||
key lastForceSitDestination; | key lastForceSitDestination; | ||
integer lastForceSitTime; | integer lastForceSitTime; | ||
integer lastPingTime; | |||
// --------------------------------------------------- | // --------------------------------------------------- | ||
Line 82: | Line 81: | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
debug(string x) | debug(string x) | ||
{ | { | ||
if (DEBUG) | |||
{ | |||
llOwnerSay("DEBUG: " + x); | |||
} | |||
} | } | ||
Line 93: | Line 95: | ||
llSay(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack); | llSay(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack); | ||
} | } | ||
// cmd begins with a '@' | // cmd begins with a '@' | ||
sendRLCmd(string cmd) | sendRLCmd(string cmd) | ||
Line 99: | Line 101: | ||
llOwnerSay(cmd); | llOwnerSay(cmd); | ||
} | } | ||
// get current mode as string | // get current mode as string | ||
string getModeDescription() | string getModeDescription() | ||
Line 107: | Line 109: | ||
else return "RLV Relay is ON (auto-accept)"; | else return "RLV Relay is ON (auto-accept)"; | ||
} | } | ||
// check that this command is for us and not someone else | // check that this command is for us and not someone else | ||
integer verifyWeAreTarget(string message) | integer verifyWeAreTarget(string message) | ||
Line 121: | Line 123: | ||
return FALSE; | return FALSE; | ||
} | } | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
// Permission Handling | // Permission Handling | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
// are we already under command by this object? | // are we already under command by this object? | ||
integer isObjectKnow(key id) | integer isObjectKnow(key id) | ||
Line 134: | Line 136: | ||
return FALSE; | return FALSE; | ||
} | } | ||
// are we already under command by this object? | // are we already under command by this object? | ||
if (kSource == id) | if (kSource == id) | ||
Line 140: | Line 142: | ||
return TRUE; | return TRUE; | ||
} | } | ||
// 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? | ||
if ((kSource == NULL_KEY) && (id == lastForceSitDestination)) | if ((kSource == NULL_KEY) && (id == lastForceSitDestination)) | ||
Line 151: | Line 153: | ||
} | } | ||
} | } | ||
return FALSE; | return FALSE; | ||
} | } | ||
// check whether the object is in llSay distance. | // check whether the object is in llSay distance. | ||
// The specification requires llSay instead of llShout or llRegionSay | // The specification requires llSay instead of llShout or llRegionSay | ||
Line 168: | Line 170: | ||
return distance <= MAX_OBJECT_DISTANCE; | 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 | ||
integer isObjectIdentityTrustworthy(key id) | integer isObjectIdentityTrustworthy(key id) | ||
Line 176: | Line 178: | ||
key object_owner=llGetOwnerKey(id); | key object_owner=llGetOwnerKey(id); | ||
key object_group=llList2Key (llGetObjectDetails (id, [OBJECT_GROUP]), 0); | key object_group=llList2Key (llGetObjectDetails (id, [OBJECT_GROUP]), 0); | ||
debug("owner= " + (string) parcel_owner + " / " + (string) object_owner); | debug("owner= " + (string) parcel_owner + " / " + (string) object_owner); | ||
debug("group= " + (string) parcel_group + " / " + (string) object_group); | debug("group= " + (string) parcel_group + " / " + (string) object_group); | ||
if (object_owner==llGetOwner () // IF I am the owner of the object | 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 | ||
Line 189: | Line 191: | ||
return FALSE; | return FALSE; | ||
} | } | ||
// 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(list list_of_commands) | integer isSimpleRequest(list list_of_commands) | ||
Line 196: | Line 198: | ||
integer len = llGetListLength(list_of_commands); | integer len = llGetListLength(list_of_commands); | ||
integer i; | integer i; | ||
debug("Checking simplicity of commands..."); | |||
// now check every single atomic command | // now check every single atomic command | ||
for (i=0; i < len; ++i) | for (i=0; i < len; ++i) | ||
Line 203: | Line 207: | ||
if (!isSimpleAtomicCommand(command)) | if (!isSimpleAtomicCommand(command)) | ||
{ | { | ||
debug("Command "+ command +" fails simplicity check."); | |||
return FALSE; | return FALSE; | ||
} | } | ||
} | } | ||
// all atomic commands passed the test | // all atomic commands passed the test | ||
return TRUE; | return TRUE; | ||
} | } | ||
// is this a simple atmar command | // is this a simple atmar command | ||
// (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(string cmd) | integer isSimpleAtomicCommand(string cmd) | ||
{ | { | ||
// check right hand side of the "=" - sign | // check right hand side of the "=" - sign | ||
integer index = llSubStringIndex (cmd, "="); | integer index = llSubStringIndex (cmd, "="); | ||
Line 222: | Line 227: | ||
// check for a number after the "=" | // check for a number after the "=" | ||
string param = llGetSubString (cmd, index + 1, -1); | string param = llGetSubString (cmd, index + 1, -1); | ||
if ((integer)param!=0 || param=="0") // is it an integer (channel number)? | if (((integer)param!=0 || param=="0") && llSubStringIndex(param, "n") <= -1 && llSubStringIndex(param, "add")<= -1) // is it an integer (channel number)? | ||
{ | { | ||
return TRUE; | return TRUE; | ||
} | } | ||
// removing restriction | // removing restriction | ||
if ((param == "y") || (param == "rem")) | if ((param == "y") || (param == "rem")) | ||
Line 233: | Line 238: | ||
} | } | ||
} | } | ||
// check for a leading ! (meta command) | // check for a leading ! (meta command) | ||
if (llSubStringIndex(cmd, PREFIX_METACOMMAND) == 0) | if (llSubStringIndex(cmd, PREFIX_METACOMMAND) == 0) | ||
Line 239: | Line 244: | ||
return TRUE; | return TRUE; | ||
} | } | ||
// check for @clear | // check for @clear | ||
// Note: @clear MUST NOT be used because the restrictions will be reapplied on next login | // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login | ||
Line 249: | Line 254: | ||
return TRUE; | return TRUE; | ||
} | } | ||
// this one is not "simple". | // this one is not "simple". | ||
return FALSE; | return FALSE; | ||
} | } | ||
// If we already have commands from this object pending | // If we already have commands from this object pending | ||
// because of a permission request dialog, just add the | // because of a permission request dialog, just add the | ||
Line 269: | Line 274: | ||
return FALSE; | return FALSE; | ||
} | } | ||
// 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 | ||
Line 275: | Line 280: | ||
integer verifyPermission(key id, string name, string message) | integer verifyPermission(key id, string name, string message) | ||
{ | { | ||
debug("Verifying permission for command "+ message); | |||
// is it switched off? | // is it switched off? | ||
if (nMode == MODE_OFF) | if (nMode == MODE_OFF) | ||
Line 280: | Line 287: | ||
return FALSE; | return FALSE; | ||
} | } | ||
// extract the commands-part | // extract the commands-part | ||
list tokens = llParseString2List (message, [","], []); | list tokens = llParseString2List (message, [","], []); | ||
Line 289: | Line 296: | ||
string commands = llList2String(tokens, 2); | string commands = llList2String(tokens, 2); | ||
list list_of_commands = llParseString2List(commands, ["|"], []); | list list_of_commands = llParseString2List(commands, ["|"], []); | ||
// accept harmless commands silently | // accept harmless commands silently | ||
if (isSimpleRequest(list_of_commands)) | if (isSimpleRequest(list_of_commands)) | ||
{ | { | ||
debug("Simple command, performing"); | |||
return TRUE; | return TRUE; | ||
} | } | ||
// 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, commands)) | if (tryToGluePendingCommands(id, commands)) | ||
{ | { | ||
return FALSE; //== | debug("Appending to store of commands pending approval."); | ||
return FALSE; //== Glue the commands and process them later | |||
} | } | ||
// check whether this object belongs here | // check whether this object belongs here | ||
integer trustworthy = isObjectIdentityTrustworthy(id); | integer trustworthy = isObjectIdentityTrustworthy(id); | ||
Line 310: | Line 319: | ||
warning = "\n\nWARNING: This object is not owned by the people owning this parcel. Unless you know the owner, you should deny this request."; | 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. | // ask in permission-request-mode and/OR in case the object identity is suspisous. | ||
if (nMode == MODE_ASK || !trustworthy) | if (nMode == MODE_ASK || !trustworthy) | ||
Line 318: | Line 327: | ||
sPendingMessage=message; | sPendingMessage=message; | ||
sPendingTime = llGetUnixTime(); | sPendingTime = llGetUnixTime(); | ||
if(llKey2Name(llGetOwnerKey(id)) != "") | llSetTimerEvent(2.0); | ||
name += " (owned by "+ llKey2Name(llGetOwnerKey(id)) +")"; | |||
if(llKey2Name(llGetOwnerKey(id)) != "") | |||
name += " (owned by "+llKey2Name(llGetOwnerKey(id))+")"; | |||
llDialog (llGetOwner(), name + " would like control your viewer." + warning + ".\n\nDo you accept ?", ["Yes", "No"], DIALOG_CHANNEL); | llDialog (llGetOwner(), name + " would like control your viewer." + warning + ".\n\nDo you accept ?", ["Yes", "No"], DIALOG_CHANNEL); | ||
debug("Asking for permission"); | debug("Asking for permission"); | ||
Line 328: | Line 339: | ||
return TRUE; | return TRUE; | ||
} | } | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
// Executing of commands | // Executing of commands | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
// execute a non-parsed message | // execute a non-parsed message | ||
// this command could be denied here for policy reasons, (if it were implemenetd) | // this command could be denied here for policy reasons, (if it were implemenetd) | ||
Line 369: | Line 380: | ||
} | } | ||
} | } | ||
// 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 | ||
Line 379: | Line 390: | ||
string param=llList2String (tokens_command, 1); // 2222 | string param=llList2String (tokens_command, 1); // 2222 | ||
integer ind=llListFindList (lRestrictions, [behav]); | integer ind=llListFindList (lRestrictions, [behav]); | ||
debug("param=" + param); | |||
if (param=="n" || param=="add") // add to lRestrictions | if (param=="n" || param=="add") // add to lRestrictions | ||
{ | { | ||
if (ind<0) lRestrictions+=[behav]; | if (ind<0) lRestrictions+=[behav]; | ||
if(kSource == NULL_KEY) | if(kSource == NULL_KEY && !lockstatus) | ||
llOwnerSay("@detach=n"); | llOwnerSay("@detach=n"); | ||
kSource=id; // we know that kSource is either NULL_KEY or id already | kSource=id; // we know that kSource is either NULL_KEY or id already | ||
} | } | ||
Line 395: | Line 408: | ||
{ | { | ||
kSource=NULL_KEY; | kSource=NULL_KEY; | ||
llOwnerSay("@detach=y"); | if(!lockstatus) | ||
llOwnerSay("@detach=y"); | |||
} | } | ||
} | } | ||
else if (param == "force" && !ALLOW_REMOVE_DETACH) | |||
{ | |||
debug("force: " + behav); | |||
list temp = llParseString2List(behav, [":"], []); | |||
string commandName = llList2String (temp, 0); // @sit | |||
if (commandName == "@detach" || commandName == "@remoutfit") | |||
{ | |||
debug("rejecting remove/detach"); | |||
llWhisper(0, "Not stripping"); | |||
ack(cmd_id, id, command, "ko"); | |||
return; | |||
} | |||
} | |||
workaroundForAtClear(command); | workaroundForAtClear(command); | ||
rememberForceSit(command); | rememberForceSit(command); | ||
Line 404: | Line 432: | ||
ack(cmd_id, id, command, "ok"); // acknowledge | ack(cmd_id, id, command, "ok"); // acknowledge | ||
} | } | ||
// check for @clear | // check for @clear | ||
// Note: @clear MUST NOT be used because the restrictions will be reapplied on next login | // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login | ||
Line 417: | Line 445: | ||
} | } | ||
} | } | ||
// remembers the time and object if this command is a force sit | // remembers the time and object if this command is a force sit | ||
rememberForceSit(string command) | rememberForceSit(string command) | ||
Line 428: | Line 456: | ||
return; | return; | ||
} | } | ||
tokens_command=llParseString2List(behav, [":"], []); | tokens_command=llParseString2List(behav, [":"], []); | ||
behav=llList2String (tokens_command, 0); // @sit | behav=llList2String (tokens_command, 0); // @sit | ||
Line 441: | Line 469: | ||
debug("remembered force sit"); | debug("remembered force sit"); | ||
} | } | ||
// 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 command) | executeMetaCommand(string cmd_id, string id, string command) | ||
Line 460: | Line 488: | ||
{ | { | ||
kSource=NULL_KEY; | kSource=NULL_KEY; | ||
llOwnerSay("@detach=y"); | if(!lockstatus) | ||
llOwnerSay("@detach=y"); | |||
integer i; | integer i; | ||
integer len=llGetListLength (lRestrictions); | integer len=llGetListLength (lRestrictions); | ||
Line 470: | Line 499: | ||
loginPendingForceSit = FALSE; | loginPendingForceSit = FALSE; | ||
} | } | ||
// --------------------------------------------------- | // --------------------------------------------------- | ||
Line 487: | Line 516: | ||
llOwnerSay (getModeDescription()); | llOwnerSay (getModeDescription()); | ||
} | } | ||
// sends the known restrictions (again) to the RL-viewer | // sends the known restrictions (again) to the RL-viewer | ||
// (call this functions on login) | // (call this functions on login) | ||
Line 507: | Line 536: | ||
} | } | ||
} | } | ||
// send a ping request and start a timer | // send a ping request and start a timer | ||
pingWorldObjectIfUnderRestrictions() | pingWorldObjectIfUnderRestrictions() | ||
Line 517: | Line 546: | ||
timerTickCounter = 0; | timerTickCounter = 0; | ||
llSetTimerEvent(1.0); | llSetTimerEvent(1.0); | ||
lastPingTime = llGetUnixTime(); | |||
loginWaitingForPong = TRUE; | loginWaitingForPong = TRUE; | ||
} | |||
} | |||
// Handle commands | |||
HandleCommand(string message, key id) | |||
{ | |||
list templist = llParseString2List(llToLower(message), [" "], []); | |||
string cmd = llList2String(templist, 0); | |||
if(cmd == "relay") | |||
{ | |||
if(id == ownerkey || (llListFindList(secowners, [id]) > -1 && (ownerkey == NULL_KEY || secaccess)) || (id == llGetOwner() && (setby == NULL_KEY || setby == llGetOwner() || (setby != ownerkey && llListFindList(secowners, [setby]) <= -1)))) | |||
{ | |||
integer change = 0; | |||
string second = llList2String(templist, 1); | |||
if(kSource != NULL_KEY && id == llGetOwner()) | |||
{ | |||
llOwnerSay("You cannot change relay modes while the relay is locked."); | |||
return; | |||
} | |||
if(id == ownerkey && (second == "secondaries" || second == "sec")) | |||
{ | |||
string third = llList2String(templist, 2); | |||
if(third == "on" || third == "auto" || (third == "" && !secaccess)) | |||
{ | |||
secaccess = 1; | |||
llWhisper(0, "Secondary owners can now adjust Restrained Life Relay settings."); | |||
} | |||
else | |||
{ | |||
secaccess = 0; | |||
llWhisper(0, "Secondary owners cannot adjust Restrained Life Relay settings."); | |||
} | |||
} | |||
else if(second == "on" || second == "auto") | |||
{ | |||
nMode = MODE_AUTO; | |||
change = 1; | |||
} | |||
else if(second == "off") | |||
{ | |||
nMode = MODE_OFF; | |||
change = 1; | |||
} | |||
else if(second == "ask") | |||
{ | |||
nMode = MODE_ASK; | |||
change = 1; | |||
} | |||
else if(second == "" || second == "mode") | |||
{ | |||
nMode++; | |||
if(nMode > 2) nMode = 0; | |||
change = 1; | |||
} | |||
if(change) | |||
{ | |||
setby = NULL_KEY; | |||
if (nMode == MODE_OFF) | |||
{ | |||
llSetTimerEvent(0.0); | |||
releaseRestrictions(); | |||
} | |||
else | |||
{ | |||
llSetTimerEvent((float)PING_INTERVAL); | |||
if(nMode == MODE_AUTO) setby = id; | |||
} | |||
if(id == llGetOwner()) | |||
llOwnerSay(getModeDescription()); | |||
else | |||
llSay(0, getModeDescription()); | |||
llMessageLinked(LINK_THIS, 63, nullstr, nullstr); | |||
} | |||
} | |||
else if(id == llGetOwner()) | |||
{ | |||
llOwnerSay("Sorry, only your owner can deactivate the relay once they enable it."); | |||
} | |||
} | } | ||
} | } | ||
Line 525: | Line 642: | ||
state_entry() | state_entry() | ||
{ | { | ||
// Request owner list from the collar | |||
llMessageLinked(LINK_THIS, 47, nullstr, nullstr); | |||
// Reset the plugin list | |||
llMessageLinked(LINK_THIS, 62, nullstr, nullstr); | |||
init(); | init(); | ||
} | } | ||
// Handle messages from the collar script | |||
link_message(integer sender, integer num, string str, key id) | |||
{ | |||
if(num == 47) | |||
{ | |||
list templist = llParseString2List(str, [","], []); | |||
integer x; | |||
integer count = llGetListLength(templist); | |||
// Handle owner list reply | |||
ownerkey = id; | |||
secowners = []; | |||
for(x=0;x<count;x++) | |||
{ | |||
secowners = secowners + [ (key)llList2String(templist, x) ]; | |||
} | |||
} | |||
// Prefixless commands | |||
else if(num == 48) | |||
{ | |||
// Handle Commands on the public or alternate channel | |||
HandleCommand(str, id); | |||
} | |||
else if(num == 33 && id != nullkey) | |||
{ | |||
// Collar script is giving us an owner | |||
ownerkey = id; | |||
} | |||
else if(num == 34 && id != nullkey) | |||
{ | |||
// Collar script is giving us a secondary owner | |||
secowners = secowners + [ id ]; | |||
} | |||
else if(num == 35) | |||
{ | |||
// Collar script is clearing owners | |||
ownerkey = nullkey; | |||
secowners = []; | |||
} | |||
else if(num == 36) | |||
{ | |||
// Collar script is clearing secondary owners | |||
secowners = []; | |||
} | |||
// Handle plugin update | |||
else if(num == 62) | |||
{ | |||
string buttons = "Relay Mode"; | |||
if(str == nullstr && (id == nullstr || id == nullkey)) | |||
{ | |||
// Add for owner and owners (key) | |||
llMessageLinked(LINK_SET, 62, "Relay Sec", buttons); | |||
// Add for sub and unowned sub (key) | |||
llMessageLinked(LINK_SET, 63, buttons, nullstr); | |||
} | |||
} | |||
else if(num == 65) | |||
{ | |||
if((integer)str == TRUE) | |||
lockstatus = 1; | |||
else | |||
lockstatus = 0; | |||
} | |||
else if(num == 66) //== Safeword, unlock | |||
{ | |||
nMode = MODE_OFF; | |||
releaseRestrictions(); | |||
llOwnerSay(getModeDescription()); | |||
} | |||
} | |||
attach(key id) | |||
{ | |||
if(id == NULL_KEY) | |||
llOwnerSay("@clear"); | |||
} | |||
on_rez(integer start_param) | on_rez(integer start_param) | ||
{ | { | ||
Line 536: | Line 735: | ||
reinforceKnownRestrictions(); | reinforceKnownRestrictions(); | ||
pingWorldObjectIfUnderRestrictions(); | pingWorldObjectIfUnderRestrictions(); | ||
llSetTimerEvent((float)PING_INTERVAL); | |||
} | } | ||
// remind the current mode to the user | // remind the current mode to the user | ||
llOwnerSay(getModeDescription()); | llOwnerSay(getModeDescription()); | ||
} | } | ||
timer() | timer() | ||
{ | { | ||
timerTickCounter++; | timerTickCounter++; | ||
debug("timer (" + (string) timerTickCounter + "): waiting for pong: " + (string) loginWaitingForPong + " pendingForceSit: " + (string) loginPendingForceSit); | debug("timer (" + (string) timerTickCounter + "): waiting for pong: " + (string) loginWaitingForPong + " pendingForceSit: " + (string) loginPendingForceSit); | ||
if (loginWaitingForPong && (timerTickCounter == | if (loginWaitingForPong && (timerTickCounter >= LOGIN_DELAY_WAIT_FOR_PONG || lastPingTime + PING_INTERVAL <= llGetUnixTime())) | ||
{ | { | ||
llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because the device is not available."); | llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because the device is not available."); | ||
Line 553: | Line 753: | ||
releaseRestrictions(); | releaseRestrictions(); | ||
} | } | ||
if (loginPendingForceSit) | if (loginPendingForceSit) | ||
{ | { | ||
Line 562: | Line 762: | ||
debug("is sitting now"); | debug("is sitting now"); | ||
} | } | ||
else if (timerTickCounter | else if (timerTickCounter >= LOGIN_DELAY_WAIT_FOR_FORCE_SIT) | ||
{ | { | ||
llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because sitting down again was not possible."); | llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because sitting down again was not possible."); | ||
Line 573: | Line 773: | ||
} | } | ||
} | } | ||
if(sPendingId != NULL_KEY) | if(sPendingId != NULL_KEY) | ||
{ | { | ||
if(sPendingTime + PERMISSION_DIALOG_TIMEOUT <= llGetUnixTime()) | if(sPendingTime + PERMISSION_DIALOG_TIMEOUT <= llGetUnixTime()) | ||
{ | { | ||
llDialog(llGetOwner(),"Request to control your viewer by "+ sPendingName +" automatically denied due to timeout.", ["OK"], DIALOG_CHANNEL); | llDialog(llGetOwner(),"Request to control your viewer by "+ sPendingName +" automatically denied due to timeout.", ["OK"], DIALOG_CHANNEL); | ||
sPendingId = NULL_KEY; | sPendingId = NULL_KEY; | ||
Line 588: | Line 787: | ||
if (!loginPendingForceSit && !loginWaitingForPong && sPendingId == NULL_KEY) | if (!loginPendingForceSit && !loginWaitingForPong && sPendingId == NULL_KEY) | ||
{ | { | ||
llSetTimerEvent( | pingWorldObjectIfUnderRestrictions(); | ||
llSetTimerEvent((float)PING_INTERVAL); | |||
} | } | ||
} | } | ||
Line 596: | Line 796: | ||
if (channel==RLVRS_CHANNEL) | if (channel==RLVRS_CHANNEL) | ||
{ | { | ||
debug("LISTEN: " + message); | |||
if (!verifyWeAreTarget(message)) | if (!verifyWeAreTarget(message)) | ||
{ | { | ||
return; | return; | ||
} | } | ||
if (nMode== MODE_OFF) | if (nMode== MODE_OFF) | ||
{ | { | ||
Line 607: | Line 808: | ||
} | } | ||
if (!isObjectNear(id)) return; | if (!isObjectNear(id)) return; | ||
debug("Got message (active world object " + (string) kSource + "): name=" + name+ "id=" + (string) id + " message=" + message); | debug("Got message (active world object " + (string) kSource + "): name=" + name+ "id=" + (string) id + " message=" + message); | ||
Line 615: | Line 816: | ||
return; | return; | ||
} | } | ||
loginWaitingForPong = FALSE; // whatever the message, it is for me => it satisfies the ping request | loginWaitingForPong = FALSE; // whatever the message, it is for me => it satisfies the ping request | ||
if (!isObjectKnow(id)) | if (!isObjectKnow(id)) | ||
{ | { | ||
Line 626: | Line 827: | ||
} | } | ||
} | } | ||
debug("Executing: " + (string) kSource); | debug("Executing: " + (string) kSource); | ||
execute(name, id, message); | execute(name, id, message); | ||
Line 642: | Line 843: | ||
execute(sPendingName, sPendingId, sPendingMessage); | execute(sPendingName, sPendingId, sPendingMessage); | ||
} | } | ||
else if(kSource == sPendingId) | |||
releaseRestrictions(); | |||
// clear pending request | // clear pending request | ||
sPendingName=""; | sPendingName=""; | ||
Line 650: | Line 853: | ||
} | } | ||
} | } | ||
changed(integer change) | changed(integer change) | ||
{ | { | ||
Line 677: | Line 862: | ||
} | } | ||
} | } | ||
</lsl> | </lsl> |
Revision as of 17:30, 24 June 2008
This version fixes a few things, like instituting an actual timeout for the ask dialog and fixing a loophole in the "ask" mode that could allow items to restrict you even if you hadn't approved them yet. This is a work in progress. Eventually it will have some of the features suggested by Marine in the original Reference Implementation.
<lsl>
//== RestrainedLife Viewer Relay Script //== by Felis Darwin //== Based on Reference Implementation by Marine Kelley
integer ALLOW_REMOVE_DETACH = TRUE; integer DEBUG = FALSE;
// --------------------------------------------------- // Amethyst Plugin Variables // ---------------------------------------------------
key nullkey = NULL_KEY; string nullstr = "";
integer secaccess=0; //== Do secondary owners have access to the RL functions?
// Internal variables key ownerkey = nullkey; list secowners = [];
key setby = NULL_KEY; //== Who set the RLV Relay status?
integer lockstatus; //== Has the collar been locked by the RLV plugin?
// --------------------------------------------------- // Constants // ---------------------------------------------------
integer RLVRS_PROTOCOL_VERSION = 1014; // version of the protocol, stated on the specification page
string PREFIX_RL_COMMAND = "@"; string PREFIX_METACOMMAND = "!";
integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers integer DIALOG_CHANNEL = -1812220409; // RLVDI in numbers
integer MAX_OBJECT_DISTANCE = 20; // 20m is llSay distance integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds
integer PERMISSION_DIALOG_TIMEOUT = 30;
integer LOGIN_DELAY_WAIT_FOR_PONG = 10; integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;
integer PING_INTERVAL = 60; //== Time between pings
integer MODE_OFF = 0; integer MODE_ASK = 1; integer MODE_AUTO = 2;
// --------------------------------------------------- // Variables // ---------------------------------------------------
integer nMode;
list lRestrictions; // restrictions currently applied (without the "=n" part) key kSource; // UUID of the object I'm commanded by, always equal to NULL_KEY if lRestrictions is empty, always set if not
string sPendingName; // name of initiator of pending request (first request of a session in mode 1) key sPendingId; // UUID of initiator of pending request (first request of a session in mode 1) string sPendingMessage; // message of pending request (first request of a session in mode 1) integer sPendingTime;
// 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 lastPingTime;
// --------------------------------------------------- // Low Level Communication // ---------------------------------------------------
debug(string x)
{
if (DEBUG) { llOwnerSay("DEBUG: " + x); }
}
// acknowledge or reject ack(string cmd_id, key id, string cmd, string ack) {
llSay(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack);
}
// cmd begins with a '@' sendRLCmd(string cmd) {
llOwnerSay(cmd);
}
// get current mode as string string getModeDescription() {
if (nMode == 0) return "RLV Relay is OFF"; else if (nMode == 1) return "RLV Relay is ON (permission needed)"; else return "RLV Relay is ON (auto-accept)";
}
// check that this command is for us and not someone else integer verifyWeAreTarget(string message) {
list tokens = llParseString2List(message, [","], []); if (llGetListLength(tokens) == 3) // this is a normal command { if (llList2String(tokens, 1) == llGetOwner()) // talking to me ? { return TRUE; } } return FALSE;
}
// --------------------------------------------------- // Permission Handling // ---------------------------------------------------
// are we already under command by this object? integer isObjectKnow(key id) {
// first some error handling if (id == NULL_KEY) { return FALSE; } // are we already under command by this object? if (kSource == id) { return TRUE; } // are we not under command by any object but were we forced to sit on this object recently? if ((kSource == 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 llSay distance.
// The specification requires llSay instead of llShout or llRegionSay
// to be used to limit the range. But this has to be checked here again
// because the objects are not trustworthy.
integer isObjectNear(key id)
{
vector myPosition = llGetRootPosition(); list temp = llGetObjectDetails(id, ([OBJECT_POS])); vector objPostition = llList2Vector(temp,0); float distance = llVecDist(objPostition, 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 // 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(list list_of_commands)
{
integer len = llGetListLength(list_of_commands); integer i; debug("Checking simplicity of commands..."); // now check every single atomic command for (i=0; i < len; ++i) { string command = llList2String(list_of_commands, i); if (!isSimpleAtomicCommand(command)) { debug("Command "+ command +" fails simplicity check."); return FALSE; } } // all atomic commands passed the test return TRUE;
}
// is this a simple atmar 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(string cmd) {
// 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") && llSubStringIndex(param, "n") <= -1 && llSubStringIndex(param, "add")<= -1) // 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 TRUE; } // 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, string commands) {
if ((sPendingId == id) && (sPendingTime + PERMISSION_DIALOG_TIMEOUT > llGetUnixTime())) { debug("Gluing " + sPendingMessage + " with " + commands); sPendingMessage = sPendingMessage + "|" + commands; return TRUE; } return FALSE;
}
// 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, string message) {
debug("Verifying permission for command "+ message); // is it switched off? if (nMode == MODE_OFF) { return FALSE; } // extract the commands-part list tokens = llParseString2List (message, [","], []); if (llGetListLength (tokens) < 3) { return FALSE; } string commands = llList2String(tokens, 2); list list_of_commands = llParseString2List(commands, ["|"], []); // accept harmless commands silently if (isSimpleRequest(list_of_commands)) { debug("Simple command, performing"); return TRUE; } // 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, commands)) { debug("Appending to store of commands pending approval."); return FALSE; //== Glue the commands and process them later } // 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 (nMode == MODE_ASK || !trustworthy) { sPendingId=id; sPendingName=name; sPendingMessage=message; sPendingTime = llGetUnixTime(); llSetTimerEvent(2.0); if(llKey2Name(llGetOwnerKey(id)) != "") name += " (owned by "+llKey2Name(llGetOwnerKey(id))+")"; llDialog (llGetOwner(), name + " would like control your viewer." + warning + ".\n\nDo you accept ?", ["Yes", "No"], DIALOG_CHANNEL); debug("Asking for permission"); return FALSE; } return TRUE;
}
// ---------------------------------------------------
// Executing of commands
// ---------------------------------------------------
// execute a non-parsed message // this command could be denied here for policy reasons, (if it were implemenetd) // but this time there will be an acknowledgement execute(string name, key id, string message) {
list tokens=llParseString2List (message, [","], []); if (llGetListLength (tokens)==3) // this is a normal command { string cmd_id=llList2String (tokens, 0); // CheckAttach key target=llList2Key (tokens, 1); // UUID if (target==llGetOwner ()) // talking to me ? { list list_of_commands=llParseString2List (llList2String (tokens, 2), ["|"], []); integer len=llGetListLength (list_of_commands); 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 (list_of_commands, i); prefix=llGetSubString (command, 0, 0); if (prefix==PREFIX_RL_COMMAND) // this is a RL command { executeRLVCommand(cmd_id, id, command); } else if (prefix==PREFIX_METACOMMAND) // this is a metacommand, aimed at the relay itself { executeMetaCommand(cmd_id, id, command); } } } }
}
// executes a command for the restrained life viewer // with some additinal magic like book keeping executeRLVCommand(string cmd_id, string id, string command) {
// we need to know whether whether is a rule or a simple command list tokens_command=llParseString2List (command, ["="], []); string behav=llList2String (tokens_command, 0); // @getattach:skull string param=llList2String (tokens_command, 1); // 2222 integer ind=llListFindList (lRestrictions, [behav]); debug("param=" + param); if (param=="n" || param=="add") // add to lRestrictions { if (ind<0) lRestrictions+=[behav];
if(kSource == NULL_KEY && !lockstatus) llOwnerSay("@detach=n"); kSource=id; // we know that kSource is either NULL_KEY or id already } else if (param=="y" || param=="rem") // remove from lRestrictions { if (ind>-1) lRestrictions=llDeleteSubList (lRestrictions, ind, ind); if (llGetListLength (lRestrictions)==0) { kSource=NULL_KEY; if(!lockstatus) llOwnerSay("@detach=y"); } } else if (param == "force" && !ALLOW_REMOVE_DETACH) { debug("force: " + behav); list temp = llParseString2List(behav, [":"], []); string commandName = llList2String (temp, 0); // @sit if (commandName == "@detach" || commandName == "@remoutfit") { debug("rejecting remove/detach"); llWhisper(0, "Not stripping"); ack(cmd_id, id, command, "ko"); return; } } workaroundForAtClear(command); rememberForceSit(command); sendRLCmd(command); // execute command ack(cmd_id, id, command, "ok"); // acknowledge
}
// 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 command) {
list tokens_command=llParseString2List (command, ["="], []); string behav=llList2String (tokens_command, 0); // @sit:<uuid> string param=llList2String (tokens_command, 1); // force if (param != "force") { return; } tokens_command=llParseString2List(behav, [":"], []); behav=llList2String (tokens_command, 0); // @sit param=llList2String (tokens_command, 1); // <uuid> debug("'force'-command:" + behav + "/" + param); if (behav != "@sit") { return; } lastForceSitDestination = (key) param; lastForceSitTime = llGetUnixTime(); debug("remembered force sit");
}
// executes a meta command which is handled by the relay itself executeMetaCommand(string cmd_id, string id, string command) {
if (command==PREFIX_METACOMMAND+"version") // checking relay version { ack(cmd_id, id, command, (string)RLVRS_PROTOCOL_VERSION); } else if (command==PREFIX_METACOMMAND+"release") // release all the restrictions (end session) { releaseRestrictions(); ack(cmd_id, id, command, "ok"); }
}
// lift all the restrictions (called by !release and by turning the relay off) releaseRestrictions () {
kSource=NULL_KEY; if(!lockstatus) llOwnerSay("@detach=y"); integer i; integer len=llGetListLength (lRestrictions); for (i=0; i<len; ++i) { sendRLCmd(llList2String (lRestrictions, i)+"=y"); } lRestrictions = []; loginPendingForceSit = FALSE;
}
// ---------------------------------------------------
// initialisation and login handling
// ---------------------------------------------------
init() {
nMode=1; kSource=NULL_KEY; lRestrictions=[]; sPendingId=NULL_KEY; sPendingName=""; sPendingMessage=""; llListen (RLVRS_CHANNEL, "", "", ""); llListen (DIALOG_CHANNEL, "", llGetOwner(), ""); llOwnerSay (getModeDescription());
}
// sends the known restrictions (again) to the RL-viewer // (call this functions on login) reinforceKnownRestrictions() {
integer i; integer len=llGetListLength(lRestrictions); string restr; debug("kSource=" + (string) kSource); for (i=0; i<len; ++i) { restr=llList2String(lRestrictions, i); debug("restr=" + restr); sendRLCmd(restr+"=n"); if (restr=="@unsit") { loginPendingForceSit = TRUE; } }
}
// send a ping request and start a timer pingWorldObjectIfUnderRestrictions() {
loginWaitingForPong = FALSE; if (kSource != NULL_KEY) { ack("ping", kSource, "ping", "ping"); timerTickCounter = 0; llSetTimerEvent(1.0); lastPingTime = llGetUnixTime(); loginWaitingForPong = TRUE; }
}
// Handle commands HandleCommand(string message, key id) {
list templist = llParseString2List(llToLower(message), [" "], []); string cmd = llList2String(templist, 0);
if(cmd == "relay") { if(id == ownerkey || (llListFindList(secowners, [id]) > -1 && (ownerkey == NULL_KEY || secaccess)) || (id == llGetOwner() && (setby == NULL_KEY || setby == llGetOwner() || (setby != ownerkey && llListFindList(secowners, [setby]) <= -1)))) { integer change = 0; string second = llList2String(templist, 1); if(kSource != NULL_KEY && id == llGetOwner()) { llOwnerSay("You cannot change relay modes while the relay is locked."); return; } if(id == ownerkey && (second == "secondaries" || second == "sec")) { string third = llList2String(templist, 2);
if(third == "on" || third == "auto" || (third == "" && !secaccess)) { secaccess = 1; llWhisper(0, "Secondary owners can now adjust Restrained Life Relay settings."); } else { secaccess = 0; llWhisper(0, "Secondary owners cannot adjust Restrained Life Relay settings."); } } else if(second == "on" || second == "auto") { nMode = MODE_AUTO; change = 1; } else if(second == "off") { nMode = MODE_OFF; change = 1; } else if(second == "ask") { nMode = MODE_ASK; change = 1; } else if(second == "" || second == "mode") { nMode++; if(nMode > 2) nMode = 0; change = 1; } if(change) { setby = NULL_KEY; if (nMode == MODE_OFF) { llSetTimerEvent(0.0); releaseRestrictions(); } else { llSetTimerEvent((float)PING_INTERVAL); if(nMode == MODE_AUTO) setby = id; } if(id == llGetOwner()) llOwnerSay(getModeDescription()); else llSay(0, getModeDescription()); llMessageLinked(LINK_THIS, 63, nullstr, nullstr); } } else if(id == llGetOwner()) { llOwnerSay("Sorry, only your owner can deactivate the relay once they enable it."); } }
}
default {
state_entry() { // Request owner list from the collar llMessageLinked(LINK_THIS, 47, nullstr, nullstr); // Reset the plugin list llMessageLinked(LINK_THIS, 62, nullstr, nullstr); init(); } // Handle messages from the collar script link_message(integer sender, integer num, string str, key id) { if(num == 47) { list templist = llParseString2List(str, [","], []); integer x; integer count = llGetListLength(templist); // Handle owner list reply ownerkey = id; secowners = []; for(x=0;x<count;x++) { secowners = secowners + [ (key)llList2String(templist, x) ]; } } // Prefixless commands else if(num == 48) { // Handle Commands on the public or alternate channel HandleCommand(str, id); } else if(num == 33 && id != nullkey) { // Collar script is giving us an owner ownerkey = id; } else if(num == 34 && id != nullkey) { // Collar script is giving us a secondary owner secowners = secowners + [ id ]; } else if(num == 35) { // Collar script is clearing owners ownerkey = nullkey; secowners = []; } else if(num == 36) { // Collar script is clearing secondary owners secowners = []; } // Handle plugin update else if(num == 62) { string buttons = "Relay Mode"; if(str == nullstr && (id == nullstr || id == nullkey)) { // Add for owner and owners (key) llMessageLinked(LINK_SET, 62, "Relay Sec", buttons); // Add for sub and unowned sub (key) llMessageLinked(LINK_SET, 63, buttons, nullstr); } } else if(num == 65) { if((integer)str == TRUE) lockstatus = 1; else lockstatus = 0; } else if(num == 66) //== Safeword, unlock { nMode = MODE_OFF; releaseRestrictions(); llOwnerSay(getModeDescription()); } } attach(key id) { if(id == NULL_KEY) llOwnerSay("@clear"); } 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 (nMode) { reinforceKnownRestrictions(); pingWorldObjectIfUnderRestrictions(); llSetTimerEvent((float)PING_INTERVAL); } // remind the current mode to the user llOwnerSay(getModeDescription()); } timer() { timerTickCounter++; debug("timer (" + (string) timerTickCounter + "): waiting for pong: " + (string) loginWaitingForPong + " pendingForceSit: " + (string) loginPendingForceSit); if (loginWaitingForPong && (timerTickCounter >= LOGIN_DELAY_WAIT_FOR_PONG || lastPingTime + PING_INTERVAL <= llGetUnixTime())) { 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 { sendRLCmd ("@sit:"+(string)kSource+"=force"); } } if(sPendingId != NULL_KEY) { if(sPendingTime + PERMISSION_DIALOG_TIMEOUT <= llGetUnixTime()) { llDialog(llGetOwner(),"Request to control your viewer by "+ sPendingName +" automatically denied due to timeout.", ["OK"], DIALOG_CHANNEL); sPendingId = NULL_KEY; sPendingName = ""; sPendingMessage = ""; } } if (!loginPendingForceSit && !loginWaitingForPong && sPendingId == NULL_KEY) { pingWorldObjectIfUnderRestrictions(); llSetTimerEvent((float)PING_INTERVAL); } } listen(integer channel, string name, key id, string message) { if (channel==RLVRS_CHANNEL) { debug("LISTEN: " + message); if (!verifyWeAreTarget(message)) { return; } if (nMode== MODE_OFF) { debug("deactivated - ignoring commands"); return; // mode is 0 (off) => reject } if (!isObjectNear(id)) return; debug("Got message (active world object " + (string) kSource + "): name=" + name+ "id=" + (string) id + " message=" + message); if (kSource != NULL_KEY && kSource != id) { debug("already used by another object => reject"); return; } loginWaitingForPong = FALSE; // whatever the message, it is for me => it satisfies the ping request if (!isObjectKnow(id)) { debug("asking for permission because kSource is NULL_KEY"); if (!verifyPermission(id, name, message)) { return; } } debug("Executing: " + (string) kSource); execute(name, id, message); } else if (channel==DIALOG_CHANNEL) { if (id != llGetOwner()) { return; // only accept dialog responses from the owner } if (sPendingId!=NULL_KEY) { if (message=="Yes") // pending request authorized => process it { execute(sPendingName, sPendingId, sPendingMessage); } else if(kSource == sPendingId) releaseRestrictions(); // clear pending request sPendingName=""; sPendingId=NULL_KEY; sPendingMessage=""; } } } changed(integer change) { if (change & CHANGED_OWNER) { llResetScript(); } }
}
</lsl>