X-Git-Url: https://feistymeow.org/gitweb/?a=blobdiff_plain;f=huffware%2Fhuffotronic_eepaw_knowledge%2Fhuff-pet_v18.6.txt;fp=huffware%2Fhuffotronic_eepaw_knowledge%2Fhuff-pet_v18.6.txt;h=1397e17c05f668292ee5fcf0309168bc19b473b9;hb=0d8edb6dc63fa915c775b005830084935f75174c;hp=0000000000000000000000000000000000000000;hpb=29190d88923f6281801872a80811faf6c2d7943e;p=feisty_meow.git diff --git a/huffware/huffotronic_eepaw_knowledge/huff-pet_v18.6.txt b/huffware/huffotronic_eepaw_knowledge/huff-pet_v18.6.txt new file mode 100755 index 00000000..1397e17c --- /dev/null +++ b/huffware/huffotronic_eepaw_knowledge/huff-pet_v18.6.txt @@ -0,0 +1,963 @@ + +// 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. +