// huffware script: huff-pet, by fred huffhines // // this is yet another implementation of a pet script in LSL. // // 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. // //to-do zone: //hmmm: for attack mode, adjust timers and ranges. // probably just define the default set and mirror that with the attack set. // switch between them during mode change. // reset timer and sensor for mode change! // end to-do zone. // constants for the pet that one might want to change. integer PET_CHAT_CHANNEL = 28; // the channel on which the pet will listen for commands from the owner. //integer DEFAULT_PANIC_DISTANCE = 28; integer DEFAULT_PANIC_DISTANCE = 5;//short leash version for opensim. // multiplied by the sensor range to get the distance allowed from the // pet to the person it's following before a teleport is invoked. integer ATTACK_PANIC_DISTANCE = 13; // multiplied by the sensor range to get the distance allowed from the // enraged pet to the attack target before a teleport is invoked. float DEFAULT_HEIGHT_ABOVE_FOLLOWED_OBJECT = 1.6; // the height that the pet will float at above whoever it's following. float ATTACK_HEIGHT_ABOVE_FOLLOWED_OBJECT = 0.0; // the height that the pet will float at above the attack target. float DEFAULT_BASE_VELOCITY = 0.1; // the velocity of the pet when just cavorting around. float ATTACK_BASE_VELOCITY = 0.6; // the velocity of the pet when in "angry" mode. integer DEFAULT_BUMP_SIZE = 48; // the default size in meters of the displacement caused by up, down, etc commands. // the margin values below are the range of motion that the pet is allowed // on each axis. float DEFAULT_X_MARGIN = 3.0; float DEFAULT_Y_MARGIN = 3.0; float DEFAULT_Z_MARGIN = 1.0; // the margin for when the pet is attacking. float ATTACK_X_MARGIN = 0.1; float ATTACK_Y_MARGIN = 0.1; float ATTACK_Z_MARGIN = 0.2; float MAXIMUM_TARGETING_DISTANCE = 2.0; // the amount of basic deviation allowed for the pet from its target spot. // this is how far it's allowed to roam from the target. //is that right? float ATTACK_PUSH_DIST_THRESHOLD = 2.0; // how close the pet should be to an attack target before trying to push. float ATTACK_PUSH_MAGNITUDE = 2147483646.0; //maxint - 1, dealing with svc-2723. // how much the critter should push an attack target. float ATTACK_PUSH_CHANCE = 0.1; // how often (probability from 0.0 to 1.0) an attack target gets pushed. // other constants that are more advanced and should generally not change... float TARGETING_SENSOR_RANGE = 96.0; // the maximum distance the pet will try to see the target at. float SENSOR_INTERVAL = 0.4; // how often the sensor scan will fire off. this is the fastest we will // check for our follow target, in seconds. float PERIODIC_INTERVAL = 0.42; // how frequently our timer event fires. integer MAXIMUM_SLACKNESS = 28; // how many timer hits we'll allow before reverting to the default state. float VELOCITY_MULTIPLIER = 1.2; // the total velocity comes from the base plus an equation that multiplies // this value by some function of the distance. string PET_MENU_NAME = "#woof"; // name for our menu. string PET_REPLY_MENU = "#aroo"; // replies with data. // symbolic labels for the different states of pet 'being'. integer STATE_STAY = 0; integer STATE_FREE = 1; // go home. integer STATE_FOLLOW = 2; integer STATE_COME = 3; integer STATE_WANDER = 4; integer STATE_ATTACK = 9; list SUPPORTED_COMMANDS = [ "go home", "come", "stay", "wander", "follow", "attack", "set home", "set name", "status" ]; // attack = follow an avatar in a menacing way. // come = follow owner. // follow = follow an avatar. // go home = return home, then move about freely. // set home = set the home position based on owner location. // set name = change the object's name. // stay = sit right there. // wander = roam a greater distance while following owner. // requires: jaunting library v3.4 or higher. ////////////// // 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. // ////////////// //requires menutini library v4.2 or better. ////////////// // do not redefine these constants. integer MENUTINI_HUFFWARE_ID = 10009; // the unique id within the huffware system for the jaunt script to // accept commands on. this is used in llMessageLinked as the num parameter. // commands available via the menu system: string SHOW_MENU_COMMAND = "#menu#"; // the command that tells menutini to show a menu defined by parameters // that are passed along. these must be: the menu name, the menu's title // (which is really the info to show as content in the main box of the menu), // the wrapped list of commands to show as menu buttons, the menu system // channel's for listening, and the key to listen to. // the reply will include: the menu name, the choice made and the key for // the avatar. // ////////////// request_full_stop() { llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, FULL_STOP_COMMAND, ""); } request_jaunt_up(integer distance) { llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, JAUNT_UP_COMMAND, (string)distance); } // global variables... key _OWNER; integer current_state; vector home_position; // the location where the pet lives. integer pending_target = FALSE; // is a target still being sought. integer _COMMAND_CHANNEL; string _COMMAND_MESSAGE = "How may I assist you?"; integer _TARGET_ID; vector TARGET_POSITION; float _FREE_RANGE = 10.0; string SIT_TEXT = "Meditate"; string SIT_ANIMATION = "yoga_float"; vector SIT_POSITION = <0.2, 0.2, 0.4>; vector SIT_ROTATION = <0.0, 0.0, 0.0>; key SITTING_AVATAR_KEY = NULL_KEY; // options for follow menu that pops up when pet is told to follow someone. list _FOLLOW_KEY; list _FOLLOW_NAME; // filled in with nearby avatars. integer _FOLLOW_CHANNEL; string _FOLLOW_MESSAGE = "Who should I follow?"; integer seeking_avatars = FALSE; key KEY_OF_TARGET; ////////////// // from hufflets... // locates the string "text" in the list to "search_in". integer find_in_list(list search_in, string text) { integer len = llGetListLength(search_in); integer i; for (i = 0; i < len; i++) { if (llList2String(search_in, i) == text) return i; } return -1; } /////////////// 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((string)debug_num + "- " + to_say); // say this on open chat, but use an unusual channel. // llSay(108, (string)debug_num + "- " + to_say); } /////////////// // info... // returns a string version of the state 's'. string name_for_state(integer s) { if (s == STATE_STAY) return "stay"; if (s == STATE_FREE) return "go home"; if (s == STATE_FOLLOW) return "follow"; if (s == STATE_COME) return "come"; if (s == STATE_WANDER) return "wander"; if (s == STATE_ATTACK) return "attack"; return "unknown"; } // menu methods... list current_buttons; // holds onto the set of menu options. integer random_channel() { return -(integer)(llFrand(40000) + 20000); } string stringize_list(list to_flatten) { return llDumpList2String(to_flatten, HUFFWARE_ITEM_SEPARATOR); } // pops up a menu to interact with the pet's owner. show_menu(string menu_name, string title, list buttons, integer channel) { current_buttons = buttons; key listen_to = _OWNER; llMessageLinked(LINK_THIS, MENUTINI_HUFFWARE_ID, SHOW_MENU_COMMAND, menu_name + HUFFWARE_PARM_SEPARATOR + title + HUFFWARE_PARM_SEPARATOR + stringize_list(current_buttons) + HUFFWARE_PARM_SEPARATOR + (string)channel + HUFFWARE_PARM_SEPARATOR + (string)listen_to); } // causes a state change to make the pet stay. enter_stay_state() { current_state = STATE_STAY; llSensorRemove(); stop_pet(); } // handle the response message when the user chooses a button. react_to_menu(integer sender, integer num, string msg, key id) { log_it("react menu: " + msg + " parm=" + (string)id); list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []); string menu_name = llList2String(parms, 0); string choice = llList2String(parms, 1); if ( (num != MENUTINI_HUFFWARE_ID + REPLY_DISTANCE) || (msg != SHOW_MENU_COMMAND) ) { log_it("why here in react to menu, not for us?"); return; } if (menu_name == PET_MENU_NAME) { log_it("react: snd=" + (string)sender + " num=" + (string)num + " msg=" + msg + " key=" + (string)id); if (find_in_list(SUPPORTED_COMMANDS, choice) < 0) { llOwnerSay("i don't know what you mean..."); return; } llOwnerSay("i heard you tell me: menu=" + menu_name + " choice=" + choice); // handle commands that are major changes in state... if (choice == "come") { llOwnerSay("coming to find you now..."); current_state = STATE_COME; llSetPrimitiveParams([PRIM_PHYSICS, TRUE, //]); PRIM_PHANTOM, TRUE]); llSensorRemove(); llSensorRepeat("", _OWNER, AGENT, TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL); } if (choice == "stay") { llOwnerSay("i will stay right here..."); enter_stay_state(); } if (choice == "wander") { current_state = STATE_WANDER; llOwnerSay("i'm going to wander around and kind of vaguely follow you..."); llSetPrimitiveParams([PRIM_PHYSICS, TRUE, //]); PRIM_PHANTOM, TRUE]); llSensorRemove(); llSensorRepeat("", _OWNER, AGENT, TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL); } if ( (choice == "follow") || (choice == "attack") ) { seeking_avatars = TRUE; stop_pet(); llSensorRemove(); if (choice == "attack") { // we only attack avatars. //or not. since that's boring. watching a pet attack a physical object is fun. llSensor("", NULL_KEY, AGENT | ACTIVE, TARGETING_SENSOR_RANGE, PI); current_state = STATE_ATTACK; } else { // look for both objects and avatars to follow. llSensor("", NULL_KEY, AGENT | ACTIVE, TARGETING_SENSOR_RANGE, PI); current_state = STATE_FOLLOW; } } if (choice == "go home") { current_state = STATE_FREE; // free to roam about the cabin, or wherever home is. llOwnerSay("i'm going home now."); jaunt_to_location(home_position); } // commands that don't lead to state changes... if (choice == "status") { string seek_addition; if (KEY_OF_TARGET != "") seek_addition = "was last seeking " + llKey2Name(KEY_OF_TARGET); llOwnerSay("my name is " + llGetObjectName() + " and state is '" + name_for_state(current_state) + "'.\n" + seek_addition); } if (choice == "set home") { list pos_list = llGetObjectDetails(llGetOwner(), [OBJECT_POS]); home_position = llList2Vector(pos_list, 0); llOwnerSay("i'm setting my home to " + (string)home_position); //hmmm: use a rounding print to show the position. } if (choice == "set name") { llOwnerSay("to change my name from " + llGetObjectName() + ",\ntell me my new name by typing:\n/" + (string)PET_CHAT_CHANNEL + " name My Cool New Name"); } } else if (menu_name == PET_REPLY_MENU) { log_it("menu-act follow: snd=" + (string)sender + " num=" + (string)num + " msg=" + msg + " id=" + (string)id); llSetPrimitiveParams([PRIM_PHYSICS, TRUE, //]); PRIM_PHANTOM, !(current_state == STATE_ATTACK)]); integer choice_indy = find_in_list(_FOLLOW_NAME, choice); if (choice_indy < 0) { //log_it("choice was not found in list"); //log_it("followname list is: " + (string)_FOLLOW_NAME); } else { string action = "follow"; if (current_state == STATE_ATTACK) action = "attack"; llOwnerSay("now " + action + "ing " + llList2String(_FOLLOW_NAME, choice_indy) + "..."); seeking_avatars = FALSE; KEY_OF_TARGET = llList2Key(_FOLLOW_KEY, choice_indy); llSensorRemove(); llSensorRepeat("", KEY_OF_TARGET, AGENT | ACTIVE, TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL); } } } // processes the hits that we get back from the sensor. the information we receive // is needed for most of the pet states. handle_sensor(integer num_detected) { if (current_state == STATE_COME) { go_to_target(_OWNER, llDetectedPos(0)); motivate(); } if ( (current_state == STATE_FOLLOW) || (current_state == STATE_ATTACK) ) { if (seeking_avatars) { // reset the list of keys and names that were found previously. _FOLLOW_KEY = []; _FOLLOW_NAME = []; // show the full set found if it will fit, otherwise just 12. integer num_to_show = num_detected; if (num_to_show > 12) num_to_show = 12; // examine each of the avatars found and put them on the list. integer i; for (i = 0 ; i < num_to_show; i++) { key to_follow = llDetectedKey(i); if (to_follow != NULL_KEY) { _FOLLOW_KEY += [to_follow]; string str = llDetectedName(i); // trim the menu item if it has hit the maximum limit. if (llStringLength(str) > 24) str = llGetSubString(str, 0, 23); integer name_try = 0; while (find_in_list(_FOLLOW_NAME, str) >= 0) { // this guy is already listed under that name, so change it a bit. str = llGetSubString(str, 0, 22) + (string)name_try++; } _FOLLOW_NAME += [str]; } } // now ask who to follow. if (llGetListLength(_FOLLOW_KEY)) { show_menu(PET_REPLY_MENU, _FOLLOW_MESSAGE, _FOLLOW_NAME, _FOLLOW_CHANNEL); } } else { // not seeking the avatar any more; follow who was chosen. go_to_target(KEY_OF_TARGET, llDetectedPos(0)); motivate(); } } if (current_state == STATE_WANDER) { if (jaunt_responses_awaited) return; // skip doing anything while we're still waiting. vector pos = llDetectedPos(0); float omg = llFrand(1) * PI * 2; float t_r = llFrand(1) * _FREE_RANGE; float t_x = t_r * llCos(omg); float t_y = t_r * llSin(omg); go_to_target(NULL_KEY, pos + ); motivate(); } } handle_timer() { if (current_state != STATE_STAY) { // make sure a bad jaunt didn't break our physics. llSetStatus(STATUS_PHYSICS, TRUE); } if (jaunt_responses_awaited) { // we are not quite there yet. if (slackness_counter++ > MAXIMUM_SLACKNESS) { // go back to the main state. we took too long. log_it("waiting for jaunt timed out."); ///argh? jaunt_responses_awaited--; slackness_counter = 0; } else return; // not time yet for rest of timed actions. } // handle the free state, since we need may to readjust the target. if (current_state == STATE_FREE) { if (pending_target) return; // haven't arrived at previous yet. vector pos = home_position; float omg = llFrand(1) * PI * 2; //hmmm: make free range settable float t_r = llFrand(1) * _FREE_RANGE; float t_x = t_r * llCos(omg); float t_y = t_r * llSin(omg); go_to_target(NULL_KEY, pos + ); motivate(); } } handle_hearing_voices(integer channel, string name, key id, string message) { if (channel != PET_CHAT_CHANNEL) return; // not our channel. //log_it("into handle voice, msg=" + message); if (id != llGetOwner()) return; // not authorized. // we found a command. which specific one? if (is_prefix(message, "up")) { // upwards bump. enter_stay_state(); string dist = llDeleteSubString(message, 0, 2); if (dist == "") dist = (string)DEFAULT_BUMP_SIZE; request_jaunt_up((integer)dist); llOwnerSay("bumping up by " + dist); } else if (is_prefix(message, "down")) { // downwards bump. enter_stay_state(); string dist = llDeleteSubString(message, 0, 4); if (dist == "") dist = (string)DEFAULT_BUMP_SIZE; request_jaunt_up(-(integer)dist); llOwnerSay("bumping down by " + dist); } else if (is_prefix(message, "jaunt")) { // zip to a specific place in the sim. enter_stay_state(); string where = llDeleteSubString(message, 0, 5); if (where == "") { llOwnerSay("i can't jaunt to like nowhere dude."); return; } vector loc = (vector)where; if (loc == <0.0, 0.0, 0.0>) { llOwnerSay("jaunt locations should be in the vector format, and jaunting to <0, 0, 0> is unsupported."); return; } llOwnerSay("jaunting to " + (string)loc); jaunt_to_location(loc); } else if (is_prefix(message, "name")) { // toss the command portion to get our new name. string new_name = llDeleteSubString(message, 0, 4); if (llStringLength(new_name) > 0) { llOwnerSay("wheeee! my new name is: " + new_name); llSetObjectName(new_name); show_title(); } else { // no data was given for the name. llOwnerSay("my name is still " + llGetObjectName()); } } else { // we support a simple translation for a few orders. if (message == "free") message = "go home"; // see if we can just flip this into a menu command instead. we don't // really care whether that works or not, since anything that doesn't work is // a bogus command. llMessageLinked(LINK_THIS, _COMMAND_CHANNEL, SHOW_MENU_COMMAND, PET_MENU_NAME + HUFFWARE_PARM_SEPARATOR + message); } } ////////////// stop_pet() { //log_it("stopping pet from moving..."); llSetPrimitiveParams([PRIM_PHYSICS, FALSE, //]); PRIM_PHANTOM, TRUE]); } go_to_target(key av, vector pos) { //log_it("told to go to target: key=" + (string)av + " pos=" + (string)pos); TARGET_POSITION = pos; if (av != NULL_KEY) { vector av_size = llGetAgentSize(av); // if it's an object, use a different method to find its height. if (av_size.z == 0.0) { // use the object's height. list box = llGetBoundingBox(KEY_OF_TARGET); float object_height = llVecDist(llList2Vector(box, 0), llList2Vector(box, 1)); av_size.z = object_height; } // adding to get pet above target. TARGET_POSITION += < 0.0, 0.0, av_size.z / 2.0>; //log_it("adjusted targposn: " + (string)TARGET_POSITION); } if (current_state == STATE_ATTACK) { TARGET_POSITION += < 0.0, 0.0, ATTACK_HEIGHT_ABOVE_FOLLOWED_OBJECT>; TARGET_POSITION += ; } else { //log_it("normal target calc"); TARGET_POSITION += < 0.0, 0.0, DEFAULT_HEIGHT_ABOVE_FOLLOWED_OBJECT>; TARGET_POSITION += ; } // trim the height a bit to keep the pet on-world. if (TARGET_POSITION.z > 4095.0) TARGET_POSITION.z = 4095.0; } integer jaunt_responses_awaited = 0; // the number of pending jumps that we are hoping will happen. integer slackness_counter; // how many snoozes we've had waiting for our destination. jaunt_to_location(vector target) { // send jaunt request to get us to the specified place. llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, JAUNT_COMMAND, (string)target); // add one to our counter so we know a jaunt is in progress. jaunt_responses_awaited++; // reset the overflow counter to recognize a new jaunt. slackness_counter = 0; } vector previous_position; // how far away target was last time. motivate() { // first, let's get into the right state of existence. llSetStatus(STATUS_PHYSICS, TRUE); // we need to be able to move around here. if (current_state == STATE_ATTACK) { llSetStatus(STATUS_PHANTOM, FALSE); // we can bonk into things now. } else { llSetStatus(STATUS_PHANTOM, TRUE); // there are no obstructive contacts. } vector current_pos = llGetPos(); float distance = llVecDist(TARGET_POSITION, current_pos); // a simple linear velocity calculation based on the object's distance. float velocity; if (current_state == STATE_ATTACK) { velocity = ATTACK_BASE_VELOCITY + VELOCITY_MULTIPLIER * (distance / 10.0); // beef the velocity up for attack mode. velocity *= 10.0; } else { velocity = DEFAULT_BASE_VELOCITY + VELOCITY_MULTIPLIER * (distance / 10.0); } //hmmm: make that 20 a constant integer jump_regardless = FALSE; if (llVecDist(current_pos, previous_position) >= 20) { // we will always re-target when the distances have changed that much; this could mean // the avatar is falling away. jump_regardless = TRUE; } float IN_RANGE_CHANCE_TO_BAIL = 0.9; float ATTACK_IN_RANGE_CHANCE_TO_BAIL = 0.5; float NEAR_RANGE_CHANCE_TO_BAIL = 0.5; float ATTACK_NEAR_RANGE_CHANCE_TO_BAIL = 0.1; if (distance <= MAXIMUM_TARGETING_DISTANCE) { // damp out the equation if the target is close enough. if (current_state == STATE_ATTACK) velocity = ATTACK_BASE_VELOCITY; else velocity = DEFAULT_BASE_VELOCITY; float within_range_chance = IN_RANGE_CHANCE_TO_BAIL; if (current_state == STATE_ATTACK) within_range_chance = ATTACK_IN_RANGE_CHANCE_TO_BAIL; if (llFrand(1.0) <= within_range_chance) return; // do nothing; close enough. } else if (distance <= 2.0 * MAXIMUM_TARGETING_DISTANCE) { // we have a bit larger chance of setting a new target if the // distance is pretty close still. float near_range_chance = NEAR_RANGE_CHANCE_TO_BAIL; if (current_state == STATE_ATTACK) near_range_chance = ATTACK_NEAR_RANGE_CHANCE_TO_BAIL; if (llFrand(1.0) <= near_range_chance) return; } previous_position = current_pos; //log_it("dist=" + (string)distance + " vel=" + (string)velocity); float time = distance / velocity; _TARGET_ID = llTarget(TARGET_POSITION, MAXIMUM_TARGETING_DISTANCE); pending_target = TRUE; // make sure we're in a physics mode before attempting physics changes... llSetStatus(STATUS_PHYSICS, TRUE); if (SITTING_AVATAR_KEY == NULL_KEY) { // when we have nobody riding, we can look wherever we want. llLookAt(TARGET_POSITION, 0.7, 0.5); } else { // if we're holding onto an avatar, we keep them pointed in a reasonable way. vector curr_pos = llGetPos(); vector new_lookat = ; llLookAt(new_lookat, 0.7, 0.5); } //log_it("setting move to target: " + (string)TARGET_POSITION); llMoveToTarget(TARGET_POSITION, time); integer panic_dist = DEFAULT_PANIC_DISTANCE; if (current_state == STATE_ATTACK) { panic_dist = ATTACK_PANIC_DISTANCE; } // don't try to jump if we're still awaiting a jump response. if (!jaunt_responses_awaited && (distance > panic_dist) ) { // we need to shorten the distance to our buddy now. jaunt_to_location(TARGET_POSITION); } else if (jump_regardless || (distance > TARGETING_SENSOR_RANGE - 10)) { // we are double our panic point, so jump even if still waiting for a reply. // however, we don't want to queue up too many jaunts at a time either. if (jaunt_responses_awaited <= 2) { jaunt_to_location(TARGET_POSITION); } } // push the attack target if we're close enough. if ( (current_state == STATE_ATTACK) && (distance < ATTACK_PUSH_DIST_THRESHOLD) ) { // only decide to push if they win the lottery here. if (llFrand(1.0) < ATTACK_PUSH_CHANCE) { llPushObject(KEY_OF_TARGET, ATTACK_PUSH_MAGNITUDE * llRot2Up(llGetRot()), ZERO_VECTOR, FALSE); } } } show_title() { llSetText(llGetObjectName(), <0.6, 0.3, 0.8>, 1.0); } // processes a link message from some other script. handle_link_message(integer which, integer num, string msg, key id) { //log_it("got msg=" + msg + " id=" + (string)id); if (num == JAUNT_HUFFWARE_ID + REPLY_DISTANCE) { //log_it("link jaunt reply"); if (msg == JAUNT_COMMAND) { jaunt_responses_awaited--; // one less response being awaited. if (jaunt_responses_awaited < 0) { log_it("erroneously went below zero for jaunt responses!"); jaunt_responses_awaited = 0; } // unpack the reply. list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []); integer last_jaunt_was_success = (integer)llList2String(parms, 0); vector posn = (vector)llList2String(parms, 1); //log_it("got a reply for a jaunt request, success=" + (string)last_jaunt_was_success + " posn=" + (string)posn); } return; } if (num != MENUTINI_HUFFWARE_ID + REPLY_DISTANCE) return; // not for us. //log_it("menu reply"); react_to_menu(which, num, msg, id); } // returns TRUE if the "prefix" string is the first part of "compare_with". integer is_prefix(string compare_with, string prefix) { return (llSubStringIndex(compare_with, prefix) == 0); } ////////////// // 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--) { //log_it("invpo=" + (string)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. //log_it("pos=" + (string)space_v_posn); } if (space_v_posn < 2) return []; // no space found. //log_it("space v@" + (string)space_v_posn); // 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--) { //log_it("indy=" + (string)space_v_posn); 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); //log_it("saw case of previously redundant item, aieee. flattened: " + to_chop_up); } } 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 []; } // ////////////// initialize() { show_title(); llSetPrimitiveParams([PRIM_PHYSICS, FALSE, //]); PRIM_PHANTOM, TRUE]); llSitTarget(SIT_POSITION, llEuler2Rot(SIT_ROTATION * DEG_TO_RAD)); llSetSitText(SIT_TEXT); llSetBuoyancy(1.0); _OWNER = llGetOwner(); _FOLLOW_KEY = []; _FOLLOW_NAME = []; current_state = STATE_FREE; TARGET_POSITION = llGetPos(); llSetTimerEvent(PERIODIC_INTERVAL); slackness_counter = 0; _COMMAND_CHANNEL = random_channel(); _FOLLOW_CHANNEL = random_channel(); _COMMAND_CHANNEL = random_channel(); llListen(PET_CHAT_CHANNEL, "", llGetOwner(), ""); home_position = llGetPos(); // start in a known place. } 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(); initialize(); } on_rez(integer param) { llResetScript(); } touch_start(integer num_detected) { show_title(); //change title to show menuing state? if (_OWNER == llDetectedKey(0)) { // show our menu here. show_menu(PET_MENU_NAME, _COMMAND_MESSAGE, SUPPORTED_COMMANDS, _COMMAND_CHANNEL); } } link_message(integer sender, integer num, string msg, key id) { handle_link_message(sender, num, msg, id); } sensor(integer num_detected) { //log_it("sensor found " + llDetectedName(0)); handle_sensor(num_detected); } no_sensor() { //use another means to find the avatar? } at_target(integer number, vector targetpos, vector ourpos) { //log_it("at target"); llTargetRemove(_TARGET_ID); pending_target = FALSE; llStopMoveToTarget(); } not_at_target() { //log_it("not at target"); } changed(integer change) { if (change & CHANGED_LINK) { key av = llAvatarOnSitTarget(); if (SITTING_AVATAR_KEY != NULL_KEY) { if (av == NULL_KEY) { llStopAnimation(SIT_ANIMATION); SITTING_AVATAR_KEY = NULL_KEY; } } else { if (av != NULL_KEY) { SITTING_AVATAR_KEY = av; llRequestPermissions(SITTING_AVATAR_KEY, PERMISSION_TRIGGER_ANIMATION); // we wish we could make the avatar a phantom here, but that's not allowed. } } } } run_time_permissions(integer perm) { key perm_key = llGetPermissionsKey(); if (perm_key == SITTING_AVATAR_KEY) { if (perm & PERMISSION_TRIGGER_ANIMATION) { list anms = llGetAnimationList(SITTING_AVATAR_KEY); integer i; for (i = 0 ; i < llGetListLength(anms) ; i++) { llStopAnimation(llList2Key(anms, i)); } llStartAnimation(SIT_ANIMATION); } } } timer() { handle_timer(); } listen(integer channel, string name, key id, string message) { handle_hearing_voices(channel, name, id, message); } } // attributions: // this script is based (a lot!) on the "pet" script that might // have been written by kazumasa loon. there was no attribution // of author in the script, but the creator was kazumasa. thanks dude! // // that being said, the script was redone a lot by fred huffhines, // mainly in the following areas: // // march or april 2008: added teleport capability to script. pet will now attempt // to keep up with the owner during follow mode by teleporting to her. // // may 2008: added ipc menu system. now menus are dealt with by the huffware // menu system, removing a lot of code from this script.