//***********************************************************************// //* Phoenix LSL bridge script version 0.10 *// //* *// //* This script has five functions: *// //* 1) Send radar informaion to the viewer *// //* 2) Retrieve true online/offline status for a requested user *// //* 3) Perform local teleports via llMoveToTarget() *// //* 4) Listen on any desired channel and return data to the viewer *// //* 5) Play a sound repeatedly *// //* *// //***********************************************************************// //**** BEGIN VARIABLES ****// integer debugger = FALSE; // TRUE to enable debugging messages integer receive_channel; // Fixed channel to receive from viewer on integer tid; // Listener ID for fixed channel integer altListenHandler = 0; // Listener handle for general listener integer listenReq; // ID of listen request from viewer integer startTime; // Time an llMoveToTarget() teleport began vector moveToTarget; // Destinstion of llMoveToTarget() teleport integer tid2; // Listener ID for random channel integer l2c; // Random channel to listen on list onlinereqs; // List of avatar keys to check online //**** END VARIABLES ****// // This function prints debugging messages if selected at the top of this // file. debug(string message) { if (debugger) { llOwnerSay("Phoenix Bridge: "+message); } } // This function initializes the script's communications channel. It'll be // reset later to a randomized value, but we have to start somewhere. The // initial channel is set from an MD5 hash of the user's UUID. init() { receive_channel = (integer)("0x"+llGetSubString( llMD5String((string)llGetOwner(),1),0,6)); debug("init: Receive channel: "+(string) receive_channel); connect(); } // This function restarts the listeners to get data from the viewer. connect() { // Remove old main listener. llListenRemove(tid); // Start new main listener. tid = llListen(receive_channel,"",llGetOwner(),""); // If there was an old secondary listener, if(l2c != 0) { // remove it, llListenRemove(tid2); // and start a new one. tid2 = llListen(l2c,"",llGetOwner(),""); }// End If // If the bridge is attached (instead of rezzed on the ground), if(llGetAttached() != 0) { // take the viewer's controls so we still work in noscript // areas. We don't actually do anything with them. llRequestPermissions(llGetOwner(),PERMISSION_TAKE_CONTROLS); }// End If } // This function sends data to the viewer, prefixed with a flag that tells // the viewer it came from the bridge. send(string data) { //if (llStringLength(data) > (1023 - 5)) // llOwnerSay("ERR: string too long"); llOwnerSay("#@#@#"+data); debug("send: Sending '"+data+"'"); } // This function returns a very large integer between 100000001 and 999999999. // Note that there will only be somewhere in the neighborhood of 24 bits // of randomness. See the Second Life Wiki's article on llFrand() for // details. integer max_rand_integer() { return (integer)(( (llFrand(0.999998) + 0.000001) + // 0.000001 through 0.999999 ((llFrand(0.899) + 0.1) * 1000) // 100 through 999 ) * 1000000); } // This function processes the command sent from the viewer. receive(string data) { // Split the message into tokens, using the | character as separator. list instruction = llParseString2List(data,["|"],[]); // The first token is the UUID of the target of the command. integer id = (integer)llList2String(instruction,0); // The second token is the command itself. string cmd = llList2String(instruction,1); // This checks the online status of an avatar. We request the status // here; the result is returned to the viewer in the dataserver event // handler. if (cmd == "online_status") { onlinereqs += [id, llRequestAgentData((key)llList2String(instruction,2), DATA_ONLINE)]; debug("receive: Processing online request"); }// End If // This retrieves the position of requested object(s) or avatar(s). // The command can request more than one position by simply listing // them, and we will return them in the same order. else if (cmd == "pos") { // Build the reply, starting with the requested UUID. list positions = [id]; // Loop through the request list and add the position of each // item to the reply list. integer increment = 2; for (;increment<(instruction!=[]);increment++) { positions += [(string)llGetObjectDetails( (key)llList2String(instruction,increment), [OBJECT_POS])]; } // Send the list to the viewer. send(llDumpList2String(positions,"|")); }// End Else If // This adds another listener on whatever channel the command // specifies, or removes the existing listener if the channel is 0. // The listen event handler just sends the received data back to the // viewer. else if (cmd == "listen") { // Save the request ID for the return value string. listenReq = id; // Remove the existing listener, if any. if (altListenHandler != 0) { llListenRemove(altListenHandler); } // Figure out what channel to listen to now. integer channelToListenOn = (integer)llList2String(instruction,2); // If we were actually given a channel, start the listener. if (channelToListenOn != 0) { altListenHandler = llListen(channelToListenOn,"",NULL_KEY,""); } }// End Else If // This will move the user to the specified position using // llMoveToTarget, breaking up a long move into steps if needed. The // real work happens in the timer event handler; this code just sets // up the actual move. else if (cmd == "move") { // Figure out where we're going. moveToTarget=(vector)llList2String(instruction,2); // Save the starting time so we don't try forever. startTime=llGetUnixTime(); // Start the timer to do the actual work. llSetTimerEvent(.05); }// End Else If // This command sets up the random high channel to use to communicate // with the viewer. else if (cmd == "l2c") { // Get an integer between 100000000 and 999999999. l2c = max_rand_integer(); // Start the listener on that channel. connect(); // Tell the viewer what channel we picked. llOwnerSay("l2c"+(string)l2c); }// End Else If // This command will play a sound repeatedly. The viewer uses this // for the "Loop sound" selection when right-clicking a sound in // inventory. else if (cmd == "loopsound") { // Get the UUID of the sound we want to play. string sound = llList2String(instruction,2); // Play it repeatedly, at full volume. llLoopSound((key)sound, 1.0); }// End Else If // This command stops the sound started by the loopsound command. else if (cmd == "stopsound") { llStopSound(); } else if(cmd == "script_count") { list lTemp; send((string)id+"|"+(string)llList2Integer(lTemp,0)+"|"+(string)((integer)(llList2Integer(lTemp=llGetObjectDetails(llList2Key(instruction,2),[OBJECT_TOTAL_SCRIPT_COUNT,OBJECT_SCRIPT_MEMORY]),1)/1024.0))); }// End Else If } //**** END FUNCTIONS ****// //**** BEGIN MAIN CODE ****// default { // This event fires when the default state is entered on script // startup. We generate the fixed channel number and then connect // to the viewer. state_entry() { llSetPrimitiveParams([PRIM_TEMP_ON_REZ, TRUE]);//Sets bridge temp. init(); } //End state entry // This event fires when the bridge object is rezzed. That happens at // initial attachment and login. We re-do the initialization to make // sure the listeners are properly set up. on_rez(integer p) { if(!llGetAttached()) { llOwnerSay("The bridge should be worn as an attachment, not rezzed. Deleting from world..."); llDie(); } init(); }// End on rez // This event fires when the server sends a message that matches the // parameters in an outstanding listen request. If it's on the // command channel, either fixed or randomized, we take the message // text and feed it to the receive() function to process. Otherwise, // it's in reply to a request to listen on some other channel; we // return the data to the viewer for processing there, prefixed with // the ID passed on the listen command. listen(integer channel, string name, key id, string message){ if(channel == receive_channel || channel == l2c) { // This is a viewer command. Deal with it. receive(message); } else { // Not a command, so just send it to the viewer. send(llDumpList2String( [listenReq,channel,name,id,message],"|")); } } //End listen // This event fires when the permissions granted to the script change. // For this script, that only happens at initialization. Normally, a // script is stopped when the user enters a no-script parcel. That's // not the case for a script that has taken the user's controls, // since it might make their behavior change drastically and // unexpectedly. We take advantage of that fact to keep running even // in no-script parcels: we take the user's controls, even though we // do nothing with them. run_time_permissions(integer p) { // Only do something if we got permissions. if(p) { // 1024 is a nonzero value that doesn't do anything // in the viewer. llTakeControls(1024,TRUE,TRUE); } } //End run time permissions // This event fires when the dataserver returns requested information. // For this script, the only information requested is online status // for avatars. dataserver(key id, string data) { // Are we checking status for the avatar we just got? integer i = llListFindList(onlinereqs,[id]); if(i != -1) { // If so, tell the viewer the status. debug("dataserver: Returning online status"); send((string)llList2Integer(onlinereqs,i-1)+"|"+data); // Remove the avatar we just reported from the list. onlinereqs = llDeleteSubList(onlinereqs,i-1,i); }// End If } //End dataserver // This event fires when the timer has expired. For this script, that // happens during a teleport within the sim when the preference // "Use llMoveToTarget TP" is selected on the Phoenix/Misc panel. At // each timer pop, we move a little closer until we're there. timer() { // Turn off the timer while we're calculating. llSetTimerEvent(0.0); // If we've been at this for more than 10 seconds, give up. if(llGetUnixTime() - 10 > startTime) { llStopMoveToTarget(); return; }// End If // Figure out where we are and where we're going. The variable // mag is the distance left to go in meters. vector us = llGetPos(); vector dist = moveToTarget - us; float mag = llVecMag(dist); // Are we there yet, daddy? If we're within a meter, call it // good. if(mag < 1.0) { // Stop moving to target so we're not frozen in place. llStopMoveToTarget(); return; }// End If // If we're more than 45 meters away, just move that much to // make sure we're within the llMoveToTarget distance limit. if(mag>45) { llMoveToTarget(us+llVecNorm(dist)*45,.05); }// End If else { // We're less than 45 meters away, so do the whole // move. llMoveToTarget(moveToTarget,.05); } //End Else // Re-enable the timer to try again. llSetTimerEvent(.05); } //End timer } //End Default //**** END MAIN CODE ****//