An update to: Sheena Desade's Sim-to-Sim Teleporter Script and Notecard (was v3.1). Includes dynamic smart menu and instant teleportation (no confirmation required) via chat link. Missing sanity checks, so format things correctly! Public domain (open-source) since April 10, 2012.
Modified by: Donjr Spiegelblatt  (May 17, 2012)
add support for:
   multi-users of the menu system at the same time
   Cancel button when only one menu is used
   support for comment lines in Data starting with # character
new features:
   PrettyButton  layout of the buttons in dialogs
   combined placeNames, simNames and locationVectors  into a two strided list called places

// Script Name: Sim-to-Sim_Pseudo-Teleporter_v41.lsl
// Author: donjr Spiegelblatt
// An update to: Sheena Desade's Sim-to-Sim Teleporter Script and Notecard (was v3.1). Includes dynamic smart menu and instant teleportation (no confirmation required) via chat link. Missing sanity checks, so format things correctly! Public domain (open-source) since April 10, 2012.
//
//This is an update of the original script from http://www.free-lsl-scripts.com/cgi/freescripts.plx?ID=1646

// Downloaded from : http://www.free-lsl-scripts.com/cgi/freescripts.plx?ID=1651

// This program is free software; you can redistribute it and/or modify it.
// Additional Licenes may apply that prevent you from selling this code
// You must leave any author credits and any headers intact in any script you use or publish.
///////////////////////////////////////////////////////////////////////////////////////////////////
// If you don't like these restrictions and licenses, then don't use these scripts.
//////////////////////// ORIGINAL AUTHORS CODE BEGINS ////////////////////////////////////////////


// modified by: Donjr Spiegelblatt  (May 17, 2012)
//add support for:
//   multi-users of the menu system at the same time
//   Cancel button when only one menu is used
//   suppot for comment lines in Data starting with # character
//new features:
//   PrettyButton  layout of the buttons in dialogs
//   combined placeNames, simNames and locationVectors  into a two strided list called places

// Modified to use SLURs by Ferd Frederix 6-7-2013
// modified by: Donjr Spiegelblatt  (May 17, 2012)
//add support for:
//   multi-users of the menu system at the same time
//   Cancel button when only one menu is used
//   suppot for comment lines in Data starting with # character
//new features:
//   PrettyButton  layout of the buttons in dialogs
//   combined placeNames, simNames and locationVectors  into a two strided list called places
// Script Name: Sim-to-Sim_Pseudo-Teleporter_v31.lsl
// Sheena Desade's Sim-to-Sim Teleporter Script and Notecard (v3.1).
// Includes dynamic smart menu and instant teleportation (no confirmation required) via chat link.
// Missing sanity checks, so format things correctly!
// Public domain (open-source) since April 10, 2012.

/* 
This script was made April 10, 2012 by Sheena Desade. It is meant only to be redistributed freely
(not ever to be sold)! Leave this header intact; other than those two requirements, do what you
will with it. And if you make an improvement, feel free to send me a copy. :-)
*/ 

/* *********************************
modified by: Donjr Spiegelblatt  (May 17, 2012)
add support for:
   multi-users of the menu system at the same time
   Cancel button when only one menu is used
   suppot for comment lines in Data starting with # character
new features:
   PrettyButton  layout of the buttons in dialogs
   combined placeNames, simNames and locationVectors  into a two strided list called places
********************************** */

// Modded by Ferd Frederix on 5-30-2013 to run in OpenSim


// ******** OPTIONAL SETTINGS **********
string hoverText    = "Sim-to-Sim Pseudo Teleporter - click for destinations.";
integer menuWait    = 30;       // How long to wait for the user to pick a menu choice
integer menuChannel = -14469;   // what channel for the object to 'listen' on.
                                // You can change this channel as needed,
                                // it's not calling out to an object outside of itself.
string menuText = "Please select your destination:";
string itemDataNotecard = "Data";
                                // The name of the notecard to read from
// ******** END OF OPTIONAL SETTINGS **********

// ******** SYSTEM SETTINGS - DO NOT MODIFY **********
// General variables
list menu_users = [];           // strided list of menu users
// [
// integer MENU_user    = 0;       // key of this menu user, unique one entry per user
integer MENU_handle  = 1;       // listen handle of this users llListen
integer MENU_timeout = 2;       // creation time of this dialog
integer MENU_curList = 3;       // the current page for this user
// ];
integer MENU_stride  = 4;       // Length of one strided of this list

// The following are required to read the notecard properly
integer notecardLine   = 0;
key currentDataRequest = NULL_KEY;
key notecarduuid       = NULL_KEY;

integer length;             // How many menu pages we have
list places;                // strided list of Placename and Sim/position

string BLANK  = " ";        // used for filler space(s) in dialogs

string Prev   = "<< Prev ";
string Cancel = "Cancel ";
string Next   = "Next >> ";
list Navigate ;     // the full navagation functions
// ******** END OF SYSTEM SETTINGS and Globals **********

list PrettyButtons(list options, list utilitybuttons)   // from SchmoDialog
{
    // returns a list formatted to that "options" will start in the top left of a dialog,
    // and "utilitybuttons" will start in the bottom right
    list spacers;
    list combined = options + utilitybuttons;
    while (llGetListLength(combined) % 3 != 0 && llGetListLength(combined) < 12)
    {
        spacers += [BLANK];
        combined = options + spacers + utilitybuttons;
    }

    return llList2List(combined, 9, 11)
         + llList2List(combined, 6,  8)
         + llList2List(combined, 3,  5)
         + llList2List(combined, 0,  2);
}

advancedMenu(key user, integer curList)
{
    integer StartTimer = (menu_users == []);       // check if the timer needs starting
    integer p = llListFindList(menu_users,[user]);
    if(~p)      // update the returning user's "creation time and curList"
        menu_users = llListReplaceList( menu_users, [llGetUnixTime(), curList], p + MENU_timeout, p+MENU_curList);
    else if(llGetListLength(menu_users) > 63)    // make sure a listen is aviable
    {
        llInstantMessage(user, "Please try again later the system is currently full!");
        return;
    }
    else        // first time menu user open there listen and create there entry
        menu_users += [user, llListen(menuChannel,"",user,""),llGetUnixTime(), curList];

    list buttons = llList2ListStrided(places,0,-1,2);   // make a list of only Places
    list utility = [Cancel];          // user should always have a Cancel option
    if (length > 1)
    {
        // We have more than one page of places.
        p = 9 * curList; // Figures out the start of the subsection of places to display
        buttons = llList2List(buttons, p, p+8);     // 'buttons' now has one to nine places
        utility = Navigate;         // give full navigation buttons
    }
    buttons = PrettyButtons(buttons, utility);
    // the 'buttons' list also now has other options besides our Places
    // and the 'utility' button(s) are always on the bottom row.

    llDialog(user,menuText,buttons,menuChannel);  // Sends a dialog to the user with the new improved button list
    if(StartTimer)
        llSetTimerEvent(5.0); // how often to check for possible timeouts, low number here would just waste processor time
}

remove(integer index, string message)
{
    // Close this users Listen
    llListenRemove(llList2Integer(menu_users, index+MENU_handle));
    // Instant message the message to the user
    llInstantMessage(llList2Key(menu_users, index), message);
    // delete there menu_users entry
    menu_users = llDeleteSubList(menu_users, index, index+MENU_stride-1);
    if(menu_users == [])        // if there are no current users
        llSetTimerEvent(0.0);       // stop the timer
}

default
{
    on_rez(integer param)
    {
        llResetScript(); // Resets script on rez
    }
   
    state_entry()
    {
        Navigate = [ Prev, Cancel, Next ];     // the full navagation functions
        llOwnerSay("Initializing...");
        llOwnerSay("Reading item data...");
        // we start reading the notecard at line 0, the first line specify our initial request
        if(llGetInventoryType(itemDataNotecard) == INVENTORY_NOTECARD)
        {
            notecardLine=0;
            currentDataRequest = llGetNotecardLine(itemDataNotecard,notecardLine);
        }
        else
        {
            state configured;            // Handle the condition of no Data notecard
        }
    }
   
    dataserver(key query, string data)
    {
        if (query == currentDataRequest) // if we are trying to read the notecard
        {
            llOwnerSay(data);
            
            currentDataRequest = NULL_KEY; // Prevent a bug that occurs with dataserver events.
            if (data == EOF) // If it the end of the file
            {
                     // Define how many pages of entries we have in the places list
                length = llGetListLength(places) / 2;
                if(length < 12)
                    length = 1;
                else
                    length = length / 9 + 1;
                llOwnerSay ("Done reading data.");
                state configured;
            }
            else
            {
                // **** IMPORTANT: I did not put any sanity checks in here, so you'll need to type
                // it all correctly, in the format "Store Name | Sim Name @ x/y/z" or it will not
                // work correctly! ****
                data = llStringTrim(data, STRING_TRIM);    // remove pesky leading and trailing whitespace
                if(llGetSubString(data,0,0) != "#" && data != "")         // lines starting with # are comments
                {
                         // We're looking for the | and @ symbol in our data line
                    list psv = llParseString2List(data,["|","@"],[]);
                    if(llGetListLength(psv) == 3) // If we found them
                    {
                            // note: Appending the BLANK make Ignore not a key word
                        string place = llStringTrim(llList2String(psv,0), STRING_TRIM)+BLANK;
                            // make sure it NOT a BLANK or one of the navigation entries
                        if(llListFindList(Navigate+[BLANK], [place]) == -1)   // these we don't want
                        {
                            string sim = llDumpList2String(llParseString2List(llStringTrim(llList2String(psv,1), STRING_TRIM), [" "], [""]), "%20");
                            // into sim erasing all internal spaces and replacing them with %20... there might be a better way to do this

                                // Generate a new temp record entry
                            list tmp = [place, sim+"/"+llStringTrim(llList2String(psv,2), STRING_TRIM)];

                            //  update entry matching on 'place' or append new entry to end of list
                            integer x = llListFindList(llList2ListStrided(places+tmp,0,-1,2),[place])*2;
                            places = llListReplaceList(places, tmp, x, x+1);

                            // We put it here so that it will not add the location unless there are also sim and placeNames.
                            // (donjr) No you put it here as you don't have all the info until this point
                        }
                    }
                    else
                    {
                        integer s = llSubStringIndex(data, "="); // Now we are looking for the = symbol
                        if(~s) // if we find it
                        {
                            string token = llToLower(llStringTrim(llDeleteSubString(data, s, -1), STRING_TRIM));
                            // use our tokens to determine which variable we are defining
                            data = llStringTrim(llDeleteSubString(data, 0, s), STRING_TRIM);
                            // use our data to define our chosen variable
                            if (token == "hover_text")
                                hoverText = data;
                            else if (token == "menu_text")
                                menuText = data;
                            else if (token == "menu_channel")
                                menuChannel = (integer)data;
                            else if (token == "selection_wait_time")
                                menuWait = (integer)data;
                        }
                    }
                }
                // Get the next line
                currentDataRequest = llGetNotecardLine(itemDataNotecard, ++notecardLine);
            }
        }
    }
    

}

state configured
{
    on_rez(integer param)
    {
        llResetScript(); // Resets script on rez
    }




    state_entry()
    {
            // collects our notecarduuid as soon as we enter this state
        notecarduuid = llGetInventoryKey(itemDataNotecard);
        if (hoverText != "none")
            llSetText(hoverText, <1.0,1.0,1.0>, 1); // if you want hovertext
        else
            llSetText("", <1.0, 1.0, 1.0>, 0); // if you do not want hovertext
        llWhisper(0, "Ready and waiting.");
    }
   
    changed(integer change)        
    {
        // We want to reload the Data notecard if it changed
        if (change & CHANGED_INVENTORY)
        {
            if(notecarduuid != llGetInventoryKey(itemDataNotecard)) // If the change was triggered by saving the NC
            {
                llOwnerSay("Notecard change detected, resetting script.");
                llResetScript(); // resets the script
            }
        }
    }

    timer()
    {
        integer dietime = llGetUnixTime() - menuWait;
        // moving backward through the list/array
        // allows us to delete records without messing up the index.
        integer index = llGetListLength(menu_users);
        while(index)
        {
            index -= MENU_stride;
            if(llList2Integer(menu_users, index+MENU_timeout) < dietime)
                remove(index, "Menu session timed out.");
        }
    }
   
    touch_start(integer num)
    {
        do {
            --num;
            advancedMenu(llDetectedKey(num), 0); // Send the user the dialog box, first page
        } while(num);
    }
   
    listen(integer index,string name,key user,string message)
    {
        // this is for the script to follow instructions based on what happens with the menu.
        index = llListFindList(menu_users,[user]);
        if(~index)          // not sure this will ever fail, but proper Bookkeeping requires it
        {
            integer curList = llList2Integer(menu_users, index + MENU_curList);
            if(message == Prev || message == Next || message == BLANK)
            {
                if(message == Prev)
                {
                    if(curList == 0)            // If we're on the first page
                        curList = length - 1;       // wrap to the last page
                    else                        // If we're not on page one
                        curList--;                  // Go backwards a page
                }
                else if(message == Next)
                    curList = (curList + 1) % length;   // this takes care of the wraping
                // else if (message == BLANK)
                //   { } // do nothing here user selected a spacer just redisplay the same menu
                advancedMenu(user, curList);          // Give the user the dialog menu
            }
            else
            {
                if(message == Cancel)
                    remove(index, "Teleport cancelled.");
                else
                {
                         // determine which location we are teleporting to
                    integer loc = llListFindList(llList2ListStrided(places,0,-1,2), [message]);
                    if(~loc) // if it's an actual location
                    {
                        loc *= 2; // convert from Record number to index
                             // Give them the link to click
                        message = "http://slurl.com/secondlife/" + llList2String(places, loc+1);


 
 
                        remove(index, "Click this link to teleport to your target location - "+message);
                    }
                    else        // on unknown message just give the dialog back to the user
                        advancedMenu(user, curList);          // Give the user the dialog menu
                }
            }
        }
    }
}