multi_destination_region_teleport.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
    if(llStringLength(data) == 0) return "";
    if(llStringLength(k) == 0) return "";
    list a = llParseStringKeepNulls(data, ["&", "="], []);
    integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
    if(i != -1) return llList2String(a, 2*i+1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
    if(llStringLength(k) == 0) return "";
    if(llStringLength(v) == 0) return "";
    if(llStringLength(data) == 0) return k + "=" + v;
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []), 
            0, -1, 2
        ), 
    [ k ]);
    if(i != -1) return llDumpList2String(
        llListReplaceList(
            llParseString2List(data, ["&"], []), 
            [ k + "=" + v ], 
        i, i), 
    "&");
    return data + "&" + k + "=" + v;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasMenuIndex = 0;
list wasDialogMenu(list input, list actions, string direction) {
    integer cut = 11-wasListCountExclude(actions, [""]);
    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
        ++wasMenuIndex;
        jump slice;
    }
    if(direction == "<" && wasMenuIndex-1 >= 0) {
        --wasMenuIndex;
        jump slice;
    }
@slice;
    integer multiple = wasMenuIndex*cut;
    input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex);
    input = wasListMerge(input, actions, "");
    return input;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasListCountExclude(list input, list exclude) {
    if(llGetListLength(input) == 0) return 0;
    if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) 
        return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
    return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasListMerge(list l, list m, string merge) {
    if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return [];
    string a = llList2String(m, 0);
    if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge);
    return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge);
}
 
list names = [];
list safe_names = [];
list coord = [];
 
key sit = NULL_KEY;
string firstname = "";
string selected = "false";
integer line = 0;
string destName = "";
vector dest = ZERO_VECTOR;
integer seconds = 30;
 
default {
    state_entry() {
        // set the origin
        llSetObjectDesc(wasKeyValueSet("origin", (string)llGetPos(), llGetObjectDesc()));
        // clear sit position and action
        llSetClickAction(CLICK_ACTION_NONE);
        llSitTarget(ZERO_VECTOR,ZERO_ROTATION);
        // read the destination notecard
        if(llGetInventoryType("destinations") != INVENTORY_NOTECARD) {
            llOwnerSay("Failed to find destinations notecard in inventory.\nPlease add a notecard containing the destinations");
            return;
        }
        llSetText("Reading destinations.", <1, 1, 0>, 1.0);
        llGetNotecardLine("destinations", line);
    }
    dataserver(key id, string data) {
        if(data == EOF) {
            llSetText("Read destinations.", <1, 0, 0>, 1.0);
            // check for consistency
            if(llGetListLength(names) != llGetListLength(coord)) {
                llSetText("Error reading destinations.", <1, 0, 0>, 1.0);
                llSetTimerEvent(1);
                return;
            }
            state select;
            return;
        }
        if(data == "") jump next_line;
        list dc = llParseString2List(data, ["#"], []);
        names += llList2String(dc, 0);
        coord += (vector)llList2String(dc, 1);
@next_line;
        llGetNotecardLine("destinations", ++line);
    }
    timer() {
        llResetScript();
    }
    changed(integer change) {
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
}
 
state select {
    state_entry() {
        llSetText("Touch me to select a destination.", <0, 1, 0>, 1.0);
    }
    touch_start(integer total_numer) {
        key click = llDetectedKey(0);
        // check if the teleport is locked to owner or unlocked
        if(wasKeyValueGet("lock", llGetObjectDesc()) == "true" && click != llGetOwner()) return;
        // block if teleporter is in use
        if(sit != NULL_KEY && click != sit) return;
        // grab user key and name
        sit = click;
        firstname = llList2String(llParseString2List(llDetectedName(0), [" "], []), 0);
        // display overhead text and set the countdown
        llSetText(firstname + " is using the teleporter (" + (string)seconds + ").", <1, 1, 0>, 1.0);
        llSetTimerEvent(1);
        // generate the list of destinations and send the dialog
        integer channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
        llListen(channel, "", sit, "");
        integer i = llGetListLength(names)-1;
        safe_names = [];
        do {
            safe_names += llGetSubString(llList2String(names, i), 0, 8);
        } while(--i>-1);
        llDialog(sit, "Choose a destination to teleport to from the provided list of destinations.", wasDialogMenu(safe_names, ["⟵ Back", "◉ Options", "Next ⟶"], ""), channel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "⟵ Back") {
            llDialog(sit, "Choose a destination to teleport to from the provided list of destinations.", wasDialogMenu(safe_names, ["⟵ Back", "◉ Options", "Next ⟶"], "<"), channel);
            return;
        }
        if(message == "Next ⟶") {
            llDialog(sit, "Choose a destination to teleport to from the provided list of destinations.", wasDialogMenu(safe_names, ["⟵ Back", "◉ Options", "Next ⟶"], ">"), channel);
            return;
        }
        if(message == "◉ Options") {
            message = "Here you can toggle the teleporter so it is restricted to the owner or not.";
            if(wasKeyValueGet("lock", llGetObjectDesc()) != "false") {
                message += "\n\nThe teleporter is currently locked to the owner.";
                jump set_message;
            }
            message += "\n\nThe teleporter is currently unlocked.";
@set_message;
            llDialog(sit, message, ["✗ Lock", "✔ Unlock", "⏏ Exit"], channel);
            return;
        }
        if(message == "✗ Lock") {
            llSetObjectDesc(wasKeyValueSet("lock", "true", llGetObjectDesc()));
            llDialog(sit, "Here you can toggle the teleporter so it is restricted to the owner or not.\n\nThe teleporter is currently locked to the owner.", ["✗ Lock", "✔ Unlock", "⏏ Exit"], channel);
            return;
        }
        if(message == "✔ Unlock") {
            llSetObjectDesc(wasKeyValueSet("lock", "false", llGetObjectDesc()));
            llDialog(sit, "Here you can toggle the teleporter so it is restricted to the owner or not.\n\nThe teleporter is currently unlocked.", ["✗ Lock", "✔ Unlock", "⏏ Exit"], channel);
            return;
        }
        if(message == "⏏ Exit") {
            llDialog(sit, "Choose a destination to teleport to from the provided list of destinations.", wasDialogMenu(safe_names, ["⟵ Back", "◉ Options", "Next ⟶"], ""), channel);
            return;
        }
        do {
            destName = llList2String(names, 0);
            if(llSubStringIndex(destName, message) != -1) {
                dest = llList2Vector(coord, 0);
                jump ready;
            }
            names = llDeleteSubList(names, 0, 0);
            coord = llDeleteSubList(coord, 0, 0);
        } while(llGetListLength(names) != 0);
        llSetText("Could not find destination.", <1, 0, 0>, 1.0);
        seconds = 1;
        return;
@ready;
        selected = "true";
        seconds = 10;
        llSitTarget(<0,0,1>,ZERO_ROTATION);
        llSetClickAction(CLICK_ACTION_SIT);
    }
    changed(integer change) {
        if(change & CHANGED_LINK) {
            key a = llAvatarOnSitTarget();
            if(a) if(a == sit) state teleport;
            llUnSit(a);
            return;
        }
        llResetScript();
    }
    timer() {
        if(selected == "true") {
            llSetText(firstname + ", touch to teleport to " + destName + " (" + (string)seconds + ").", <1, 1, 0>, 1.0);
            jump continue;
        }
        if(selected == "false") {
            llSetText(firstname + " is using the teleporter (" + (string)seconds + ").", <1, 1, 0>, 1.0);
            jump continue;
        }
@continue;
        if(--seconds == 0) llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
}
 
state teleport {
    state_entry() {
        // set timeout
        llSetTimerEvent(10);
        // clear sit position and action
        llSetRegionPos(dest);
        // request to set camera
        llRequestPermissions(sit, PERMISSION_CONTROL_CAMERA);
    }
    run_time_permissions(integer perm) {
        // set the camera so the viewer follows the agent
        // this works if the viewer is not zoomed-in on
        // the teleport pad
        if(perm & PERMISSION_CONTROL_CAMERA) {
            llSetCameraParams([
                CAMERA_ACTIVE, 1,
                CAMERA_BEHINDNESS_ANGLE, 45.0,
                CAMERA_BEHINDNESS_LAG, 0.5,
                CAMERA_DISTANCE, 8.0,
                CAMERA_FOCUS_LAG, 0.05 ,
                CAMERA_FOCUS_LOCKED, TRUE,
                CAMERA_FOCUS_THRESHOLD, 0.0,
                CAMERA_PITCH, 20.0,
                CAMERA_POSITION_LAG, 0.1,
                CAMERA_POSITION_LOCKED, TRUE,
                CAMERA_POSITION_THRESHOLD, 0.0,
                CAMERA_FOCUS_OFFSET, <3,0,2>
            ]);
            llReleaseCamera(sit);
        }
        llSetTimerEvent(0.05);
    }
    timer() {
        key a = llAvatarOnSitTarget();
        if(a) llUnSit(a);
        llSetRegionPos((vector)wasKeyValueGet("origin", llGetObjectDesc()));
    }
    on_rez(integer num) {
        llSetRegionPos((vector)wasKeyValueGet("origin", llGetObjectDesc()));
        llResetScript();
    }
    changed(integer change) {
        llSetRegionPos((vector)wasKeyValueGet("origin", llGetObjectDesc()));
        llResetScript();
    }
}