LSLClient Bridge v0.13

Written by Kitsune
//***********************************************************************//
//* 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 ****//