~ Or How Grid-Wide Vehicle Teleports Came to Be ~
Please note that the prototype is a flying vehicle, however, the concept explained in this article should work for any vehicle type.
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…
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…
Here's a breakdown of the steps:
[K] ROFLCopter - Control Rezzer
script.[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.[K] ROFLCopter - Drive
forces a teleport of the avatar to those coordinates using RLV.[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.
1
and drop the [K] ROFLCopter - Drive
and [K] ROFLCopter - Sit
in the vehicle.[K] ROFLCopter - Drive
to include your destinations.[K] ROFLCopter - Control Rezzer
inside the HUD.[K] ROFLCopter - Control Rezzer
script.[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.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.
/////////////////////////////////////////////////////////////////////////// // 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(); } }
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.
/////////////////////////////////////////////////////////////////////////// // 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); } }
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.
/////////////////////////////////////////////////////////////////////////// // 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)); } }