// huffware script: jaunting library, by fred huffhines, released under GPL-v3 license. // // this script is a library of useful teleportation and movement calls. it should be added // into an object along with other scripts that use the library. the jaunting library // responds to linked messages as commands. // parts of this script are based on warpPos from "Teleporter Script v 3.0 by Asira Sakai", // which was found at the LSL wiki. // // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html // do not use it in objects without fully realizing you are implicitly accepting that license. // // global constants... float PROXIMITY_REQUIRED = 0.1; // how close we must be to the target location to call the jaunt done. // we make this fairly low accuracy since we don't want jumps in space to immediately // be counted as failures. // the most jumps the script will try to take. the overall distance from the start // to the end can be 10 * MAXIMUM_JUMPS_PER_CALL meters. integer MAXIMUM_JUMPS_PER_CALL = 84; // global variables... vector last_posn; // used to calculate jump distances. // jaunting library link message API... ////////////// // do not redefine these constants. integer JAUNT_HUFFWARE_ID = 10008; // the unique id within the huffware system for the jaunt script to // accept commands on. this is used in llMessageLinked as the num parameter. string HUFFWARE_PARM_SEPARATOR = "{~~~}"; // this pattern is an uncommon thing to see in text, so we use it to separate // our commands in link messages. string HUFFWARE_ITEM_SEPARATOR = "{|||}"; // used to separate lists of items from each other when stored inside a parameter. // this allows lists to be passed as single string parameters if needed. integer REPLY_DISTANCE = 100008; // offset added to service's huffware id in reply IDs. // commands available via the jaunting library: string JAUNT_COMMAND = "#jaunt#"; // command used to tell jaunt script to move object. pass a vector with the location. string FULL_STOP_COMMAND = "#fullstop#"; // command used to bring object to a halt. string REVERSE_VELOCITY_COMMAND = "#reverse#"; // makes the object reverse its velocity and travel back from whence it came. string SET_VELOCITY_COMMAND = "#setvelocity#"; // makes the velocity equal to the vector passed as the first parameter. string JAUNT_UP_COMMAND = "#jauntup#"; string JAUNT_DOWN_COMMAND = "#jauntdown#"; // commands for height adjustment. pass a float for number of meters to move. string JAUNT_LIST_COMMAND = "#jauntlist#"; // like regular jaunt, but expects a list of vectors as the first parameter; this list // should be in the jaunter notecard format (separated by pipe characters). // the second parameter, if any, should be 1 for forwards traversal and 0 for backwards. ////////////// // returns what we consider to be safe to do in one big jump. float distance_safe_to_jaunt() { return (float)(MAXIMUM_JUMPS_PER_CALL - 4) * 10.0; } // tests whether the destination is safe for an object to enter. integer safe_for_entry(vector where) { if (outside_of_sim(where)) { // that's an obvious wrong idea; it tends to break us. return FALSE; } return TRUE; } // helper function for warp_across_list. this adds one jump vector to the // list of rules as long as the destination is interesting. list process_warp_item(vector next_jump) { //log_it("mem: " + (string)llGetFreeMemory()); // calculate the number of jumps needed. integer jumps = (integer)(llVecDist(next_jump, last_posn) / 10.0) + 1; last_posn = next_jump; // record for next check. // set up our list which we'll replicate. list rules = [ PRIM_POSITION, next_jump ]; // Try and avoid stack/heap collisions. if (jumps > MAXIMUM_JUMPS_PER_CALL - 1) jumps = MAXIMUM_JUMPS_PER_CALL; // add the rules repeatedly to get the effective overall jump done. integer count = 1; while ( (count = count << 1) < jumps) rules += rules; // magnify the rule list before really adding it. this gets us to the proper // final number of jumps. return rules + llList2List(rules, (count - jumps) << 1 - 2, count); } // originally based on warpPos from lsl wiki but drastically modified. // uses a set of transfer points instead of a single target. list warp_across_list(list full_journey, integer forwards) { // make sure the list didn't run out. if (llGetListLength(full_journey) == 0) return []; if (forwards) { // forwards traversal of the list. vector next_jump = (vector)llList2String(full_journey, 0); // shortcut the jumps if we're already there. if (next_jump == llGetPos()) return warp_across_list(chop_list(full_journey, 1, llGetListLength(full_journey) - 1), forwards); // calculate our trajectory for the next jump and add in all subsequent jumps. return process_warp_item(next_jump) + warp_across_list(chop_list(full_journey, 1, llGetListLength(full_journey) - 1), forwards); } else { // reverse traversal of the list. vector next_jump = (vector)llList2String(full_journey, llGetListLength(full_journey) - 1); // shortcut the jumps if we're already there. if (next_jump == llGetPos()) return warp_across_list(chop_list(full_journey, 0, llGetListLength(full_journey) - 2), forwards); // calculate our trajectory for the next jump and add in all subsequent jumps. return process_warp_item(next_jump) + warp_across_list(chop_list(full_journey, 0, llGetListLength(full_journey) - 2), forwards); } } // the main function that performs the jaunting process. // now also supports adding a rotation into the mix to avoid paying the extra delay // cost of calling llSetRot. jaunt(list full_journey, integer forwards, string command_used, integer use_rotation, rotation rot, integer reply_to_request) { //log_it("jaunt: fullj=" + (string)full_journey + " forew=" + (string)forwards); // set up our global variables... last_posn = llGetPos(); // calculate the trip and run it. list rot_add; if (use_rotation) rot_add = [ PRIM_ROTATION, rot ]; llSetPrimitiveParams(warp_across_list(full_journey, forwards) + rot_add); if (reply_to_request) { // pick out the last target in the list based on the direction we're moving. integer last_indy = 0; if (forwards == TRUE) last_indy = llGetListLength(full_journey) - 1; vector last_jump = (vector)llList2String(full_journey, last_indy); // final judge of success here is how close we got to the target. integer landed_there = (llVecDist(llGetPos(), last_jump) <= PROXIMITY_REQUIRED); send_reply(LINK_THIS, [landed_there, llGetPos()], command_used); } } ////////////// // sets the object's speed to "new_velocity". // if "local_axis" is TRUE, then it will be relative to the object's // own local coordinates. set_velocity(vector new_velocity, integer local_axis) { vector current_velocity = llGetVel(); if (local_axis) { rotation rot = llGetRot(); current_velocity /= rot; // undo the rotation. } new_velocity -= current_velocity; new_velocity *= llGetMass(); llApplyImpulse(new_velocity, local_axis); } // attempts to bring the object to a complete stop. full_stop() { llSetForce(<0,0,0>, FALSE); set_velocity(<0,0,0>, FALSE); } // sends an object back along its trajectory. reverse_velocity(integer local_axis) { vector current_velocity = llGetVel(); if (local_axis) { rotation rot = llGetRot(); current_velocity /= rot; // undo the rotation. } vector new_velocity = -2 * current_velocity; new_velocity *= llGetMass(); llApplyImpulse(new_velocity, local_axis); } // teleports to the new "location", if possible. does not change object's phantom / physical // states. this will do the jaunt in multiple steps if the distance from here to "location" // is too large. apportioned_jaunt(vector location, string command_used, integer use_rotation, rotation rot, integer should_send_reply) { if (!safe_for_entry(location)) { // that's not good. we should not allow the script to get broken. if (should_send_reply) send_reply(LINK_THIS, [FALSE, llGetPos()], command_used); return; } // go to position specified, by leapfrogs if too long. integer chunk_of_jump; integer MAX_CHUNKS = 200; // multiplies the distance a single jaunt can cover. for (chunk_of_jump = 0; chunk_of_jump < MAX_CHUNKS; chunk_of_jump++) { vector interim_vec = location - llGetPos(); float jump_dist = llVecDist(llGetPos(), location); integer reply_needed = TRUE; if (jump_dist > distance_safe_to_jaunt()) { // the entire distance cannot be jumped. do the part of it that fits. float proportion_can_do = distance_safe_to_jaunt() / jump_dist; interim_vec *= proportion_can_do; reply_needed = FALSE; // don't reply when this is not full jump. } interim_vec += llGetPos(); // bias jump back to where we are. //log_it("jumping from " + (string)llGetPos() + " to " + (string)interim_vec); jaunt([llGetPos(), interim_vec], TRUE, command_used, use_rotation, rot, reply_needed && should_send_reply); float dist_now = llVecDist(llGetPos(), interim_vec); if (dist_now > PROXIMITY_REQUIRED) { //log_it("failed to make interim jump, dist left is " + (string)dist_now); // bail out. we failed to get as far as we thought we should. chunk_of_jump = MAX_CHUNKS + 10; if (!reply_needed) { // we need to send the reply we hadn't sent yet. if (should_send_reply) send_reply(LINK_THIS, [FALSE, llGetPos()], command_used); } } else if (llVecDist(llGetPos(), location) <= PROXIMITY_REQUIRED) { // leave loop for a different reason; we got there. chunk_of_jump = MAX_CHUNKS + 10; if (!reply_needed) { // we need to send the reply we hadn't sent yet. if (should_send_reply) send_reply(LINK_THIS, [TRUE, llGetPos()], command_used); } } } } // the entire distance embodied in the list of targets. this is a little smart, // in that if there's only one target, we assume we're going from "here" to that // one target. float total_jaunt_distance(list targets) { if (!llGetListLength(targets)) return 0.0; // add in "this" location if they omitted it. if (llGetListLength(targets) < 2) targets = [ llGetPos() ] + targets; integer disindy; float total_dist = 0.0; vector prev = (vector)llList2String(targets, 0); for (disindy = 1; disindy < llGetListLength(targets); disindy++) { vector next = (vector)llList2String(targets, disindy); total_dist += llVecDist(prev, next); prev = next; } return total_dist; } // jaunts to a target via a set of intermediate locations. can either go forwards // through the list or backwards. phantom_jaunt_to_list(list targets, integer forwards, string command_used, integer use_rotation, rotation rot) { vector final_loc; if (forwards) final_loc = (vector)llList2String(targets, llGetListLength(targets) - 1); else final_loc = (vector)llList2String(targets, 0); //hmmm: check each destination in list?? if (!safe_for_entry(final_loc)) { // that's not good. we should not allow the script to get broken. send_reply(LINK_THIS, [FALSE, llGetPos()], command_used); return; } // this turns off the physics property for the object, so that jaunt and // llSetPos will still work. this became necessary due to havok4. integer original_phantomosity = llGetStatus(STATUS_PHANTOM); integer original_physicality = llGetStatus(STATUS_PHYSICS); if (original_physicality != FALSE) llSetStatus(STATUS_PHYSICS, FALSE); if (original_phantomosity != TRUE) llSetStatus(STATUS_PHANTOM, TRUE); integer send_reply_still = TRUE; // true if we should send a reply when done. // iterate through our list of targets and either we will jaunt to the next // place directly or we will have to wrap a few destinations due to sim crossing. while (llGetListLength(targets) > 0) { vector next_loc; if (forwards) next_loc = (vector)llList2String(targets, 0); else next_loc = (vector)llList2String(targets, llGetListLength(targets) - 1); if (outside_of_sim(next_loc)) { log_it("bad jaunt path: first target is out of sim."); send_reply(LINK_THIS, [FALSE, llGetPos()], command_used); return; // skip that badness. } // check how much total distance we have in the path that's left. if it's under our // limit, then we'll just take it in one jump. float total_dist = total_jaunt_distance(targets); // if we're below the threshold, we'll just jump now. integer already_jumped = FALSE; if (total_dist < distance_safe_to_jaunt()) { jaunt(targets, forwards, command_used, use_rotation, rot, TRUE); targets = []; // reset the list now. send_reply_still = FALSE; // we have already sent the reply in jaunt(). already_jumped = TRUE; // don't do anything else. } if (!already_jumped) { vector next_plus_1 = ZERO_VECTOR; // default cannot fail our "is inside sim" check. if (llGetListLength(targets) > 1) { if (forwards) next_plus_1 = (vector)llList2String(targets, 1); else next_plus_1 = (vector)llList2String(targets, llGetListLength(targets) - 2); } if (outside_of_sim(next_plus_1)) { //hmmm: eventually find all the negative ones in a row and do them in combo, rather than // just giving up here and doing a jaunt to the rest.. jaunt(targets, forwards, command_used, use_rotation, rot, TRUE); targets = []; // reset the list now. send_reply_still = FALSE; // we have already sent the reply in jaunt(). } else { // we've passed the negativity test, so we can at least jump to the next place. // zap the next location, since we're about to jump there. integer zap_pos = 0; if (!forwards) zap_pos = llGetListLength(targets) - 1; targets = llDeleteSubList(targets, zap_pos, zap_pos); // only bother jumping if we're not already there. if (llVecDist(next_loc, llGetPos()) > PROXIMITY_REQUIRED) { apportioned_jaunt(next_loc, command_used, use_rotation, rot, FALSE); } } } } if (send_reply_still) { integer yippee = TRUE; // assume we succeeded until we prove otherwise. if (llVecDist(final_loc, llGetPos()) > PROXIMITY_REQUIRED) yippee = FALSE; send_reply(LINK_THIS, [yippee, llGetPos()], command_used); } // return to prior characteristics. if (original_phantomosity != TRUE) llSetStatus(STATUS_PHANTOM, original_phantomosity); if (original_physicality != FALSE) llSetStatus(STATUS_PHYSICS, original_physicality); } // implements our API for jumping around. handle_link_message(integer sender, integer huff_id, string msg, key id) { // separate the list out list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []); if (msg == JAUNT_COMMAND) { // use list to string to avoid non-conversions. vector v = (vector)llList2String(parms, 0); rotation rot = <0.0, 0.0, 0.0, 1.0>; integer gave_rot = llGetListLength(parms) > 1; if (gave_rot) rot = (rotation)llList2String(parms, 1); // ooh, they gave us a rotational value also. // log_it("gave rot? " + (string)gave_rot + " rot=" + (string)rot); phantom_jaunt_to_list([llGetPos(), v], TRUE, msg, gave_rot, rot); } else if (msg == FULL_STOP_COMMAND) { full_stop(); } else if (msg == REVERSE_VELOCITY_COMMAND) { reverse_velocity(FALSE); } else if (msg == SET_VELOCITY_COMMAND) { vector v = (vector)llList2String(parms, 0); //log_it("jaunting lib received set velocity request for " + (string)v); set_velocity(v, FALSE); } else if (msg == JAUNT_UP_COMMAND) { phantom_jaunt_to_list([ llGetPos(), llGetPos() + <0.0, 0.0, llList2Float(parms, 0)> ], TRUE, msg, FALSE, ZERO_ROTATION); } else if (msg == JAUNT_DOWN_COMMAND) { phantom_jaunt_to_list([ llGetPos(), llGetPos() + <0.0, 0.0, -llList2Float(parms, 0)> ], TRUE, msg, FALSE, ZERO_ROTATION); } else if (msg == JAUNT_LIST_COMMAND) { string destination_list = llList2String(parms, 0); list targets = llParseString2List(destination_list, [HUFFWARE_ITEM_SEPARATOR], []); destination_list = ""; integer forwards = TRUE; // snag the directionality for the list, if specified. if (llGetListLength(parms) > 1) forwards = llList2Integer(parms, 1); rotation rot = <0.0, 0.0, 0.0, 1.0>; integer gave_rot = llGetListLength(parms) > 2; if (gave_rot) rot = llList2Rot(parms, 2); // ooh, they gave us a rotational value also. phantom_jaunt_to_list(targets, forwards, msg, gave_rot, rot); targets = []; } } ////////////// // from hufflets... integer debug_num = 0; // a debugging output method. can be disabled entirely in one place. log_it(string to_say) { debug_num++; // tell this to the owner. llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say); // say this on open chat, but use an unusual channel. // llSay(108, llGetScriptName() + "--" + (string)debug_num + ": " + to_say); } ////////////// // a simple version of a reply for a command that has been executed. the parameters // might contain an outcome or result of the operation that was requested. send_reply(integer destination, list parms, string command) { llMessageLinked(destination, JAUNT_HUFFWARE_ID + REPLY_DISTANCE, command, llDumpList2String(parms, HUFFWARE_PARM_SEPARATOR)); } // returns TRUE if the value in "to_check" specifies a legal x or y value in a sim. integer valid_sim_value(float to_check) { if (to_check < 0.0) return FALSE; if (to_check >= 257.0) return FALSE; return TRUE; } integer outside_of_sim(vector to_check) { return !valid_sim_value(to_check.x) || !valid_sim_value(to_check.y); } /////////////// // returns the portion of the list between start and end, but only if they are // valid compared with the list length. an attempt to use negative start or // end values also returns a blank list. list chop_list(list to_chop, integer start, integer end) { integer last_len = llGetListLength(to_chop) - 1; if ( (start < 0) || (end < 0) || (start > last_len) || (end > last_len) ) return []; return llList2List(to_chop, start, end); } ////////////// ////////////// // huffware script: auto-retire, by fred huffhines, version 2.5. // distributed under BSD-like license. // !! keep in mind that this code must be *copied* into another // !! script that you wish to add auto-retirement capability to. // when a script has auto_retire in it, it can be dropped into an // object and the most recent version of the script will destroy // all older versions. // // the version numbers are embedded into the script names themselves. // the notation for versions uses a letter 'v', followed by two numbers // in the form "major.minor". // major and minor versions are implicitly considered as a floating point // number that increases with each newer version of the script. thus, // "hazmap v0.1" might be the first script in the "hazmap" script continuum, // and "hazmap v3.2" is a more recent version. // // example usage of the auto-retirement script: // default { // state_entry() { // auto_retire(); // make sure newest addition is only version of script. // } // } // this script is partly based on the self-upgrading scripts from markov brodsky // and jippen faddoul. ////////////// auto_retire() { string self = llGetScriptName(); // the name of this script. list split = compute_basename_and_version(self); if (llGetListLength(split) != 2) return; // nothing to do for this script. string basename = llList2String(split, 0); // script name with no version attached. string version_string = llList2String(split, 1); // the version found. integer posn; // find any scripts that match the basename. they are variants of this script. for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) { string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn); if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) { // found a basic match at least. list inv_split = compute_basename_and_version(curr_script); if (llGetListLength(inv_split) == 2) { // see if this script is more ancient. string inv_version_string = llList2String(inv_split, 1); // the version found. // must make sure that the retiring script is completely the identical basename; // just matching in the front doesn't make it a relative. if ( (llList2String(inv_split, 0) == basename) && ((float)inv_version_string < (float)version_string) ) { // remove script with same name from inventory that has inferior version. llRemoveInventory(curr_script); } } } } } // // separates the base script name and version number. used by auto_retire. list compute_basename_and_version(string to_chop_up) { // minimum script name is 2 characters plus a version. integer space_v_posn; // find the last useful space and 'v' combo. for (space_v_posn = llStringLength(to_chop_up) - 3; (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v"); space_v_posn--) { // look for space and v but do nothing else. } if (space_v_posn < 2) return []; // no space found. // now we zoom through the stuff after our beloved v character and find any evil // space characters, which are most likely from SL having found a duplicate item // name and not so helpfully renamed it for us. integer indy; for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) { if (llGetSubString(to_chop_up, indy, indy) == " ") { // found one; zap it. since we're going backwards we don't need to // adjust the loop at all. to_chop_up = llDeleteSubString(to_chop_up, indy, indy); } } string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1); // ditch the space character for our numerical check. string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1); // strip out a 'v' if there is one. if (llGetSubString(chop_suffix, 0, 0) == "v") chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1); // if valid floating point number and greater than zero, that works for our version. string basename = to_chop_up; // script name with no version attached. if ((float)chop_suffix > 0.0) { // this is a big success right here. basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1); return [ basename, chop_suffix ]; } // seems like we found nothing useful. return []; } // ////////////// default { state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; } on_rez(integer parm) { state rerun; } } state rerun { state_entry() { state default; } } state real_default { state_entry() { auto_retire(); } link_message(integer sender, integer huff_id, string msg, key id) { if (huff_id != JAUNT_HUFFWARE_ID) return; // not our responsibility. handle_link_message(sender, huff_id, msg, id); } }