~ Or How Grid-Wide Vehicle Teleports Came to Be ~

Video

Please note that the prototype is a flying vehicle, however, the concept explained in this article should work for any vehicle type.

The Problem

So, you got yourself this awesome new vehicle! However, you drive it to the margin of the SIM and you hit an invisible wall. You keep trying and trying but there is no way to just break those limits. Well, now there is…

Introduction

This script was tested and works on OpenSim version 0.7.4!

RLV has always been one of my favorite APIs to play with. I see it as an extension to LSL that does not have to be limited exclusively to sexual play. LSL allows developers to do a limited number of things and RLV extends that by working on the bridge between LSL and the viewer. If you ever wondered whether RLV is safe or compromises your privacy, it does not. In fact, seen from privacy's perspective, it can only limit the amount of information you give out. RLV is an essential component and maintained by third party viewers and compensates with ToS compliant logic where Linden could not assume responsibility even if they wanted to.

In this article, I will use RLV and a set of scripts to transfer a vehicle and its owner to a region of their choice. I do that by using just two RLV commands:

@sit

which make the owner of the script sit on an object, and

@tpto

which teleports an avatar given a set of coordinates.

Just these two RLV commands are used in the script and allow us to teleport. The rest is a little bit of wizardry and magic…

Requirements and Limitations

  • All the regions you set as a landing point must have build on (this is not much more than the limitation to rez your vehicle in the first place).
  • A vehicle with COPY permissions.
  • MODIFY permissions in case you are not the creator of the vehicle and you want to apply this upgrade to your vehicle.
  • A RLV compatible viewer with RLV turned on.

How it Works

Here's a breakdown of the steps:

  1. You have a warp HUD which contains the exact replica of your vehicle.
  2. When you sit in your vehicle and click the HUD, you get a menu of destinations which are configurable in the script in the HUD (alternatively, for developers, this could be done perhaps via a notecard). This is done by the [K] ROFLCopter - Control Rezzer script.
  3. When you select a destination from the menu, the [K] ROFLCopter - Control Rezzer script sends that destination to the ROFLCopter and is received by the [K] ROFLCopter - Drive script which performs the necessary calculations. It adds the selected region location coordinates with the local offsets in that region sent by the HUD.
  4. After doing that, the [K] ROFLCopter - Drive forces a teleport of the avatar to those coordinates using RLV.
  5. When the avatar arrives at that location, the HUD rezzes a new vehicle.
  6. When the vehicle rezzes, the [K] ROFLCopter - Sit starts to spin and try to force the avatar to sit down in the vehicle.

As you can see, the scripts are not really teleporting the vehicle, just the avatar and then rezzing the vehicle again from the HUD and force-sitting the avatar onto the vehicle. The avatar however, may unsit from the vehicle at any time since the scripts cease to send RLV force-sits to the avatar once the avatar has sat down at least once. This make sense because after a vehicle teleport, your avatar should be sitting inside the vehicle. If you decide to unsit, that would usually be after the teleport, not before.

Setting Up

  1. Create a vehicle. There are a number of scripts out there which take care of that and it is unnecessary to post one here.
  2. Create a HUD. To do this, create a new primitive on the ground, right click it and select attach to HUD and choose a position.
  3. Open the vehicle you created at point 1 and drop the [K] ROFLCopter - Drive and [K] ROFLCopter - Sit in the vehicle.
  4. Modify the [K] ROFLCopter - Drive to include your destinations.
  5. Open the HUD and drop the [K] ROFLCopter - Control Rezzer inside the HUD.
  6. Now, if the vehicle is as you want it to be, take the vehicle to your inventory.
  7. Open the HUD, and drop the entire vehicle in the HUD, next to the [K] ROFLCopter - Control Rezzer script.
  8. Rez your vehicle from inventory, sit in it and have fun teleporting back and forth between the destinations.

Ideas for Developers

  • The coordinates are set manually in the [K] ROFLCopter - Drive script. However, I am sure that it can be done in a better way to allow quickly adding and deleting destinations. This could perhaps be done via notecards or with some external storage script that could act as a bookmark manager.
  • One problem is that when the avatar rezzes at the destination, it is not set to fly. It would be ideal if the avatar stood still until the vehicles rezzes, perhaps for aesthetics more than anything else. If you find a way, feel free to contribute.
  • The cut-scene is annoying sometimes. The delay between appearing at the destination and the vehicle forcing you to sit can look bad for perfectionists. If that is the case, how about you take a look at the RLV API and look for those viewer effects (I've seen them used before to create drugs). You could blur the screen of the avatar, perhaps even getting closer to that typical Star Trek effect, until the avatar sits down. That way, they will not see the cut-scene.
  • This prototype demonstrates a one-person vehicle. However, it is possible, yet probably tricky, to transport several people at once.

Code: [K] ROFLCopter - Drive

This is pretty much the Restrained Love API forced teleport example with some modifications. This script goes inside the vehicle and is responsible for teleporting the avatar to the destination they chose.

roflcopterdrive.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
// Inspired by:
// http://wiki.secondlife.com/wiki/LSL_Protocol/RestrainedLoveAPI
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasStringToVector(string in) {
    list v = llParseString2List(in, ["<", ",", ">"], []);
    return <llList2Float(v, 0), llList2Float(v, 1), llList2Float(v, 2)>;
}
 
key sQuery = NULL_KEY;
vector reqPos = ZERO_VECTOR;
vector gPos = ZERO_VECTOR;
 
default
{
  state_entry () {
    integer comChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
    llListen (comChannel+4, "", "", "");
  }
 
  on_rez(integer start_param) {
    llResetScript();
  }
 
  listen(integer channel, string name, key id, string message) {
    list tokens = llParseString2List (message, ["/"], []);
    if (llGetListLength (tokens)!=4) return;
    reqPos.x = llList2Float (tokens, 1);
    reqPos.y = llList2Float (tokens, 2);
    reqPos.z = llList2Float (tokens, 3);
    sQuery=llRequestSimulatorData (llList2String (tokens, 0), DATA_SIM_POS);
  }
 
  dataserver(key queryid, string data) {
    if (queryid != sQuery) return;
    gPos = wasStringToVector(data);
    gPos += reqPos;
    llOwnerSay("Aye aye cp'n! Going to warp in 10 seconds...");
    llSetTimerEvent(10);
  }
 
  timer() {
      llSetTimerEvent(0);
      llOwnerSay("@tpto:" + (string)((integer)gPos.x) + "/" +(string)((integer)gPos.y) +"/" +(string)((integer)gPos.z) + "=force");
      llDie();
  }
 
}

Code: [K] ROFLCopter - Sit

This script is placed inside the vehicle and is responsible to sit the avatar down on the vehicle as soon as the vehicle has rezzed at the destination.

roflcoptersit.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
default
{
    changed(integer change) {
        if(change & CHANGED_LINK) if(llGetAgentInfo(llGetOwner()) & AGENT_ON_OBJECT) llResetScript();
    }
    on_rez(integer param) {
        if(param == (((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF)+18) llSetTimerEvent(.3);
    }
    timer() {
        llSetTimerEvent(0);
        if(llGetAgentInfo(llGetOwner())) {
            llOwnerSay("@sit:"+(string)llGetKey()+"=force");
        }
        llSetTimerEvent(.3);
    }
 
}

Code: [K] ROFLCopter - Control Rezzer

This script goes inside the HUD and offers the destination menu once clicked. It is also responsible for rezzing the vehicle once it reaches the destination.

roflcopterrezzer.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                   CONFIGURATION                      //
//////////////////////////////////////////////////////////
//                                                      //
// You can add or remove destinations to this list. To 
// add a destination, you need the region name and a 
// position on that SIM. Best is, for now, to just rez 
// a box somewhere and place it where your vehicle's 
// landing point should be. Then add them to this list, 
// using the format:
//
// Region Name/x/y/z
//
// Where x, y an z represent the coordinates of the 
// primitive you rezzed. You can look at the example 
// regions adde below for help.
 
list JUMPS = ["New Orleans Island/109/119/509", 
              "12th Man/40/56/759"];
//                                                      //
//                  END CONFIGURATION                   //
//////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//                              INTERNALS                                //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    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);
}
 
integer comChannel = 0;
integer comHandle = 0;
list menu_items = [];
string destinations = "";
 
default
{
    state_entry() {
        menu_items = [];
        destinations = "";
        integer i = llGetListLength(JUMPS)-1;
        do {
            destinations += (string)i + ".) " + llList2String(llParseString2List(llList2String(JUMPS, i), ["/"], []), 0) + "\n";
            menu_items += (string)i;
        } while(--i>-1);
    }
 
    changed(integer change) {
        if(change & CHANGED_REGION) {
            llSetForce((<.0,.0,9.82> * llGetObjectMass(llGetOwner())),FALSE);
            integer comChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
            if(llGetInventoryNumber(INVENTORY_OBJECT)) llRezObject(llGetInventoryName(INVENTORY_OBJECT, 0), llGetPos(), ZERO_VECTOR, ZERO_ROTATION, comChannel+18);
            llSensorRepeat("", NULL_KEY, AGENT, .1, .1, 1); 
        }
    }
 
    no_sensor() {
        if(llGetAgentInfo(llGetOwner()) & AGENT_ON_OBJECT) llSetForce(ZERO_VECTOR, FALSE);
    }
 
    touch_start(integer total_number) {
        integer comChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        comHandle = llListen(comChannel, "", llGetOwner(), "");
        llDialog(llGetOwner(), "\nPlease choose your destination:\n" + destinations, wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], ""), comChannel);
        llSetTimerEvent(30);
    }
    timer() {
        llSetTimerEvent(0);
        llListenRemove(comHandle);
        llOwnerSay("Sorry, menu timeout...");
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "<= Back") {
            llSetTimerEvent(60);
            llDialog(id, "\nPlease choose your destination:\n" + destinations,  wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], "<"), channel);
            return;
        }
        if(message == "Next =>") {
            llSetTimerEvent(60);
            llDialog(id, "\nPlease choose your destination:\n" + destinations,  wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], ">"), channel);
            return;
        }
        llOwnerSay(llList2String(JUMPS, (integer)message));
        llRegionSay(channel+4, llList2String(JUMPS, (integer)message));
    }
}

Obtaining the Prototype

  • Prototype available at our store. Full permission and free.

secondlife/warp_core.txt · Last modified: 2022/11/24 07:46 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.