/////////////////////////////////////////////////////////////////////////// // 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. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // CONFIGURATION // /////////////////////////////////////////////////////////////////////////// // The channel on which Jump! listens - you can change this to some number // greater or equal to zero and then send commands by prefixing the channel // number. For example: /5 @mark new in case you set the channel to 5. integer LISTEN_CHANNEL = 0; /////////////////////////////////////////////////////////////////////////// // INTERNALS // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Second Life Constants // // PrimFS™ Physical Parameters. // /////////////////////////////////////////////////////////////////////////// // The maximum number of bytes that we can store in a description is set // here to 127 bytes which is the current number of bytes that you can // store in a primitive's description in Second Life. integer BYTES_PER_SECTOR = 127; // Primitives can have a zero-length description but that can only be // accomplished by setting the description to the empty string using a // script. Otherwise, any primitive created with actually have this // string in the description indicating that no description is set. string UNFORMATTED_MARKER = "(No Description)"; /////////////////////////////////////////////////////////////////////////// // Wizardry and Steamworks // // API @ grimore.org // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// list wasCompoundToList(integer T, list compound) { integer S = llGetListEntryType(compound, 0); if(S != TYPE_VECTOR && S != TYPE_ROTATION) return []; list a = llParseString2List((string)compound, ["<", ",", ">"], []); compound = []; do { if(T == TYPE_FLOAT) compound += llList2Float(a, 0); if(T == TYPE_INTEGER) compound += llList2Integer(a, 0); if(T == TYPE_STRING) compound += llList2String( llParseString2List((string)a, [" "], []) , 0); a = llDeleteSubList(a, 0, 0); } while(llGetListLength(a) != 0); return compound; } /////////////////////////////////////////////////////////////////////////// // 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(data) == 0) return k + "=" + v; if(llStringLength(k) == 0) return ""; if(llStringLength(v) == 0) return ""; 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) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueDelete(string k, string data) { if(llStringLength(data) == 0) return ""; if(llStringLength(k) == 0) return ""; integer i = llListFindList( llList2ListStrided( llParseString2List(data, ["&", "="], []), 0, -1, 2 ), [ k ]); if(i != -1) return llDumpList2String( llDeleteSubList( llParseString2List(data, ["&"], []), i, i), "&"); return data; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// list wasKeyValueGetKeys(string data) { return llList2ListStrided( llParseString2List( data, ["&", "="], [] ), 0, -1, 2 ); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueEncode(list data) { integer i = llGetListLength(data); if (i % 2 != 0 || i == 0) return ""; --i; do { data = llListInsertList( llDeleteSubList( data, i-1, i ), [ llList2String(data, i-1) + "=" + llList2String(data, i) ], i-1 ); i -= 2; } while(i > 0); return llDumpList2String(data, "&"); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // returns the size of a linked primitive description string wasGetLinkDescription(integer link) { string d = (string)llGetLinkPrimitiveParams(link, [PRIM_DESC]); if(d == UNFORMATTED_MARKER || d == "") return ""; return d; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // sets the description of a linked primitive wasSetLinkDescription(integer link, string description) { llSetLinkPrimitiveParamsFast(link, [PRIM_DESC, description]); } /////////////////////////////////////////////////////////////////////////// // PrimFS™ API // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Remove all descriptors k and all data associated with descriptor k // from head to tail and return the number of bytes deleted (excluding // the length of the descriptor k). integer wasPrimFSDelete(string k, integer head, integer tail) { integer b; do { string d = wasGetLinkDescription(tail); if(llStringLength(d) == 0) jump continue; b += llStringLength(wasKeyValueGet(k, d)); wasSetLinkDescription(tail, wasKeyValueDelete( k, d ) ); // GC d = ""; @continue; } while(--tail>=head); return b; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Returns a list containing all unique file descriptors. list wasPrimFSGetDescriptors(integer head, integer tail) { list descriptors = []; do { string d = wasGetLinkDescription(tail); if(llStringLength(d) == 0) jump continue; list c = llList2ListStrided( llParseString2List(d, ["&", "="], []), 0, -1, 2 ); // GC d = ""; do { string k = llList2String(c, 0); if(llListFindList( descriptors, (list)k ) != -1) jump skip; descriptors += k; // GC k = ""; @skip; c = llDeleteSubList(c, 0, 0); } while(llGetListLength(c) != 0); // GC c = []; @continue; } while(--tail>=head); return descriptors; } /////////////////////////////////////////////////////////////////////////// // PrimFS™ Read-Write // // Descriptor Level. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Writes data to the filesystem in the partition between ead and tail // and returns the number of written bytes. // // data cannot contain the = (equal, ASCII 061) sign, a restriction of // key-value data structure (http://grimore.org/secondlife:key_value_data). integer wasPrimFSWrite(string k, string data, integer head, integer tail) { integer b; do { string d = wasGetLinkDescription(tail); string v = ""; if(llSubStringIndex(d, k) != -1) v = wasKeyValueGet(k, d); integer s = llStringLength(v); // if the key was found if(s != 0) { s = BYTES_PER_SECTOR - llStringLength(d) + s; v = llGetSubString(data, 0, s-1); if(llStringLength(v) != 0) jump write; wasSetLinkDescription(tail, wasKeyValueDelete( k, d ) ); jump continue; } // if the key was not found if(llStringLength(d) >= BYTES_PER_SECTOR) jump continue; s = BYTES_PER_SECTOR - llStringLength(d) - llStringLength(k) - 1; // & if(llStringLength(d) != 0) --s; if(s < 0) jump continue; v = llGetSubString(data, 0, s-1); if(llStringLength(v) == 0) jump continue; // write @write; b += llStringLength(v) + llStringLength(k) + 1; v = wasKeyValueSet(k, v, d); wasSetLinkDescription(tail,v); // GC v = ""; data = llDeleteSubString(data, 0, s-1); @continue; // GC d = ""; } while(--tail>=head); // return written bytes return b; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Reads the filesystem on the partition between head and tail and returns // the data associated with the file descriptor k. string wasPrimFSRead(string k, integer head, integer tail) { string output = ""; do { string d = wasGetLinkDescription(tail); if(llStringLength(d) == 0) jump continue; if(llListFindList(wasKeyValueGetKeys(d), [ k ]) == -1) jump continue; output += wasKeyValueGet(k, d); @continue; // GC d = ""; } while(--tail>=head); return output; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Formats all blocks for PrimFS™ between head and tail and returns the // number of sectors for the new partition. // The resulting formatted partions can be found between head and tail. integer wasPrimFSFormat(integer head, integer tail) { integer seek = tail; do { wasSetLinkDescription(seek, ""); } while(--seek>=head); return tail-head; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Returns the number of free space (in bytes) on a partition bounded // by head and tail integer wasPrimFSGetFreeSpace(integer head, integer tail) { integer occupied = 0; integer seek = tail; do { occupied += llStringLength(wasGetLinkDescription(seek)); } while(--seek>=head); return BYTES_PER_SECTOR*(tail-head)-occupied; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Returns the number of free space (in bytes) on a partition bounded // by head and tail integer wasPrimFSGetUsedSpace(integer head, integer tail) { integer occupied = 0; do { occupied += llStringLength(wasGetLinkDescription(tail)); } while(--tail>=head); return occupied; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// vector magVector(vector i, float mag) { i.x *= 2; i.y *= 2; i.z *= 2; if(llFabs(i.x) < mag && llFabs(i.y) < mag && llFabs(i.z) < mag) return magVector(i, mag); return i/2; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// wasTellOwner(string type, string message) { llOwnerSay("⎣" + type + "⎤: " + message + "."); } // The following wrapper is purely aesthentic wrapper that will make sure // that no_sensor will //always// be triggered after one second. wasSensorAttach(integer t) { llSensorRepeat("", NULL_KEY, AGENT, .1, .1, t); } vector rp = ZERO_VECTOR; vector gridPos = ZERO_VECTOR; string jumpTarget = ""; integer target = 0; default { state_entry() { llSetTimerEvent(30); integer c = 1 + (integer)llFrand(9); llListen(c, "", llGetOwner(), ""); llOwnerSay("@version=" + (string)c); } listen(integer channel, string name, key id, string message) { llSetTimerEvent(0); // Don't detach. llOwnerSay("@detach=n"); state main; } timer() { llSetTimerEvent(0); wasTellOwner("jump!", "your viewer is not RLV-enabled. This gizmo requires a RLV-enabled viewer"); } on_rez(integer num) { llResetScript(); } attach(key id) { llResetScript(); } } state main { state_entry() { // owner channel llListen(LISTEN_CHANNEL, "", llGetOwner(), ""); } listen(integer i, string top, key id, string message) { if(id != llGetOwner()) return; // only accept "@" jump prefix list command = llParseString2List(message, [" "], ["@"]); if(llList2String(command, 0) != "@") return; command = llDeleteSubList(command, 0, 0); // second, check if we have a bookmark request list descriptors = wasPrimFSGetDescriptors(2, llGetNumberOfPrims()); do { string o = llList2String(descriptors, 0); if(llSubStringIndex(o, llList2String(command, 0)) == -1) jump continue_mark; list tokens = llParseString2List(wasPrimFSRead(o, 2, llGetNumberOfPrims()), ["/"], []); if (llGetListLength(tokens) != 4) jump continue_mark; jumpTarget = llList2String(tokens, 0); tokens = llDeleteSubList(tokens, 0, 0); if (jumpTarget == llGetRegionName() && llVecDist((vector)("<" + llList2CSV(tokens) + ">"), llGetPos()) < 1) { wasTellOwner("jump", "destination too close"); return; } // we found the mark, so proceed to teleport rp = (vector)("<" + llList2CSV(tokens) + ">"); llOwnerSay("@unsit=force"); llRequestSimulatorData(jumpTarget, DATA_SIM_STATUS); wasTellOwner("jump", llGetRegionName() + " ↦ " + jumpTarget); return; @continue_mark; descriptors = llDeleteSubList(descriptors, 0, 0); } while(llGetListLength(descriptors) != 0); if(llList2String(command, 0) == "help") { llGiveInventory(llGetOwner(), "Jump! Cheatsheet"); return; } if(llList2String(command, 0) == "wipe") state wipe; if(llList2String(command, 0) == "free") { integer free = wasPrimFSGetFreeSpace(2, llGetNumberOfPrims()); integer used = wasPrimFSGetUsedSpace(2, llGetNumberOfPrims()); wasTellOwner("free", "Free: " + (string)free + "b / Used: " + (string)used + "b / Occupation: " + (string)((integer)(100.0 * (float)used/(float)(free + used))) + "%"); return; } if(llList2String(command, 0) == "unlock") { llOwnerSay("@detach=y"); wasTellOwner("unlock", "jump! is now unlocked, you can detach the object from inventory"); return; } if(llList2String(command, 0) == "lock") { llOwnerSay("@detach=n"); wasTellOwner("lock", "jump! is now locked"); return; } if(llList2String(command, 0) == "list") { list d = wasPrimFSGetDescriptors(2, llGetNumberOfPrims()); do { string k = llList2String(d, 0); string v = wasPrimFSRead(k, 2, llGetNumberOfPrims()); list t = llParseString2List(v, ["/"], []); if(llGetListLength(t) != 4) jump continue_list; if(llList2String(t, 0) == llGetRegionName()) v = "⚉ " + v; wasTellOwner(k, v); @continue_list; d = llDeleteSubList(d, 0, 0); } while(llGetListLength(d) != 0); return; } // command with one parameter top = llList2String(command, 0); command = llDeleteSubList(command, 0, 0); if(llGetListLength(command) == 0) return; message = llList2String(command, 0); command = llDeleteSubList(command, 0, 0); if(llStringLength(message) == 0) { wasTellOwner(llList2String(command, 0), "requires a parameter"); return; } if(top == "search") { // check for syntax violation. if(llSubStringIndex(message, "&") != -1 || llSubStringIndex(message, "=") != -1) { wasTellOwner("mark", "mark names may not contain the ampersand (&) or equal (=) characters"); return; } list d = wasPrimFSGetDescriptors(2, llGetNumberOfPrims()); do { string k = llList2String(d, 0); string v = wasPrimFSRead(k, 2, llGetNumberOfPrims()); if(llGetListLength( llParseString2List(v, ["/"], [])) != 4 ) jump continue_search; if(llSubStringIndex( llList2String(d, 0), message) != -1 || llSubStringIndex(v, message) != -1 ) wasTellOwner(k, v); @continue_search; d = llDeleteSubList(d, 0, 0); } while(llGetListLength(d) != 0); return; } if(top == "mark") { // check for overwrite. if(llListFindList(wasPrimFSGetDescriptors(2, llGetNumberOfPrims()), (list)message) != -1) { wasTellOwner("mark", "marking would overwrite previously stored mark"); return; } // check for syntax violation. if(llSubStringIndex(message, "&") != -1 || llSubStringIndex(message, "=") != -1) { wasTellOwner("mark", "mark names may not contain the ampersand (&) or equal (=) characters"); return; } if(wasPrimFSWrite(message, llGetRegionName() + "/" + llDumpList2String( wasCompoundToList(TYPE_INTEGER, [llGetPos()]), "/"), 2, llGetNumberOfPrims()) == 0) { wasTellOwner("mark", "could not write mark"); return; } wasTellOwner("mark", "mark set"); return; } if(top == "unmark") { // check for syntax violation. if(llSubStringIndex(message, "&") != -1 || llSubStringIndex(message, "=") != -1) { wasTellOwner("mark", "mark names may not contain the ampersand (&) or equal (=) characters"); return; } i = llListFindList(wasPrimFSGetDescriptors(2, llGetNumberOfPrims()), (list)message); if(i == -1) { wasTellOwner("unmark", "mark not found"); return; } if(wasPrimFSDelete(message, 2, llGetNumberOfPrims())) { wasTellOwner("unmark", "mark removed"); return; } wasTellOwner("unmark", "no mark removed"); return; } // did not recognize command } dataserver(key queryid, string data) { if(data == "up") { llRequestSimulatorData(jumpTarget, DATA_SIM_POS); return; } if(data == "down" || data == "starting" || data == "crashed" || data == "unknown") { wasTellOwner("jump", "simulator state is not ready, refusing to jump"); return; } gridPos = rp + (vector)data; state teleport; } on_rez(integer num) { llResetScript(); } attach(key id) { llResetScript(); } } state teleport { state_entry() { // schedule the jump llSetTimerEvent(1); } timer() { // wait RLV unsit window if(llGetAgentInfo(llGetOwner()) & (AGENT_ON_OBJECT|AGENT_SITTING)) return; // jump! llSetTimerEvent(0); llOwnerSay("@tpto:" + llDumpList2String(wasCompoundToList(TYPE_INTEGER, [gridPos]), "/") + "=force"); wasSensorAttach(5); } changed(integer change) { if(change & CHANGED_REGION == 0) return; state default; } no_sensor() { state default; } on_rez(integer num) { llResetScript(); } attach(key id) { llResetScript(); } } state wipe { state_entry() { integer c = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); llListen(c, "", llGetOwner(), ""); llSetTimerEvent(30); llDialog(llGetOwner(), "Are you sure you want to wipe your entire drive? By clicking confirm, you will delete all stored landmarks. This process is irreversible and you would need to set all your landmarks again. Consider making a copy of Jump! before proceeding.", ["✔ CONFIRM", "✖ CANCEL"], c); } listen(integer channel, string name, key id, string message) { if(message == "✔ CONFIRM") { wasPrimFSFormat(2, llGetNumberOfPrims()); wasTellOwner("wipe", "drive formatted"); state default; } wasTellOwner("wipe", "aborted"); state default; } timer() { wasTellOwner("wipe", "aborted, dialog timed out, please reissue the command"); state default; } on_rez(integer num) { llResetScript(); } attach(key id) { llResetScript(); } }