2 // huffware script: huff-pet, by fred huffhines
4 // this is yet another implementation of a pet script in LSL.
6 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
7 // do not use it in objects without fully realizing you are implicitly accepting that license.
12 //hmmm: for attack mode, adjust timers and ranges.
13 // probably just define the default set and mirror that with the attack set.
14 // switch between them during mode change.
15 // reset timer and sensor for mode change!
19 // constants for the pet that one might want to change.
21 integer PET_CHAT_CHANNEL = 28;
22 // the channel on which the pet will listen for commands from the owner.
24 //integer DEFAULT_PANIC_DISTANCE = 28;
25 integer DEFAULT_PANIC_DISTANCE = 5;//short leash version for opensim.
26 // multiplied by the sensor range to get the distance allowed from the
27 // pet to the person it's following before a teleport is invoked.
28 integer ATTACK_PANIC_DISTANCE = 13;
29 // multiplied by the sensor range to get the distance allowed from the
30 // enraged pet to the attack target before a teleport is invoked.
32 float DEFAULT_HEIGHT_ABOVE_FOLLOWED_OBJECT = 1.6;
33 // the height that the pet will float at above whoever it's following.
34 float ATTACK_HEIGHT_ABOVE_FOLLOWED_OBJECT = 0.0;
35 // the height that the pet will float at above the attack target.
37 float DEFAULT_BASE_VELOCITY = 0.1;
38 // the velocity of the pet when just cavorting around.
39 float ATTACK_BASE_VELOCITY = 0.6;
40 // the velocity of the pet when in "angry" mode.
42 integer DEFAULT_BUMP_SIZE = 48;
43 // the default size in meters of the displacement caused by up, down, etc commands.
45 // the margin values below are the range of motion that the pet is allowed
47 float DEFAULT_X_MARGIN = 3.0;
48 float DEFAULT_Y_MARGIN = 3.0;
49 float DEFAULT_Z_MARGIN = 1.0;
50 // the margin for when the pet is attacking.
51 float ATTACK_X_MARGIN = 0.1;
52 float ATTACK_Y_MARGIN = 0.1;
53 float ATTACK_Z_MARGIN = 0.2;
55 float MAXIMUM_TARGETING_DISTANCE = 2.0;
56 // the amount of basic deviation allowed for the pet from its target spot.
57 // this is how far it's allowed to roam from the target.
60 float ATTACK_PUSH_DIST_THRESHOLD = 2.0;
61 // how close the pet should be to an attack target before trying to push.
62 float ATTACK_PUSH_MAGNITUDE = 2147483646.0; //maxint - 1, dealing with svc-2723.
63 // how much the critter should push an attack target.
64 float ATTACK_PUSH_CHANCE = 0.1;
65 // how often (probability from 0.0 to 1.0) an attack target gets pushed.
67 // other constants that are more advanced and should generally not change...
69 float TARGETING_SENSOR_RANGE = 96.0;
70 // the maximum distance the pet will try to see the target at.
72 float SENSOR_INTERVAL = 0.4;
73 // how often the sensor scan will fire off. this is the fastest we will
74 // check for our follow target, in seconds.
76 float PERIODIC_INTERVAL = 0.42;
77 // how frequently our timer event fires.
79 integer MAXIMUM_SLACKNESS = 28;
80 // how many timer hits we'll allow before reverting to the default state.
82 float VELOCITY_MULTIPLIER = 1.2;
83 // the total velocity comes from the base plus an equation that multiplies
84 // this value by some function of the distance.
86 string PET_MENU_NAME = "#woof"; // name for our menu.
87 string PET_REPLY_MENU = "#aroo"; // replies with data.
89 // symbolic labels for the different states of pet 'being'.
90 integer STATE_STAY = 0;
91 integer STATE_FREE = 1; // go home.
92 integer STATE_FOLLOW = 2;
93 integer STATE_COME = 3;
94 integer STATE_WANDER = 4;
95 integer STATE_ATTACK = 9;
97 list SUPPORTED_COMMANDS = [ "go home", "come", "stay",
98 "wander", "follow", "attack",
99 "set home", "set name", "status" ];
100 // attack = follow an avatar in a menacing way.
101 // come = follow owner.
102 // follow = follow an avatar.
103 // go home = return home, then move about freely.
104 // set home = set the home position based on owner location.
105 // set name = change the object's name.
106 // stay = sit right there.
107 // wander = roam a greater distance while following owner.
109 // requires: jaunting library v3.4 or higher.
111 // do not redefine these constants.
112 integer JAUNT_HUFFWARE_ID = 10008;
113 // the unique id within the huffware system for the jaunt script to
114 // accept commands on. this is used in llMessageLinked as the num parameter.
115 string HUFFWARE_PARM_SEPARATOR = "{~~~}";
116 // this pattern is an uncommon thing to see in text, so we use it to separate
117 // our commands in link messages.
118 string HUFFWARE_ITEM_SEPARATOR = "{|||}";
119 // used to separate lists of items from each other when stored inside a parameter.
120 // this allows lists to be passed as single string parameters if needed.
121 integer REPLY_DISTANCE = 100008; // offset added to service's huffware id in reply IDs.
123 // commands available via the jaunting library:
124 string JAUNT_COMMAND = "#jaunt#";
125 // command used to tell jaunt script to move object. pass a vector with the location.
126 string FULL_STOP_COMMAND = "#fullstop#";
127 // command used to bring object to a halt.
128 string REVERSE_VELOCITY_COMMAND = "#reverse#";
129 // makes the object reverse its velocity and travel back from whence it came.
130 string SET_VELOCITY_COMMAND = "#setvelocity#";
131 // makes the velocity equal to the vector passed as the first parameter.
132 string JAUNT_UP_COMMAND = "#jauntup#";
133 string JAUNT_DOWN_COMMAND = "#jauntdown#";
134 // commands for height adjustment. pass a float for number of meters to move.
135 string JAUNT_LIST_COMMAND = "#jauntlist#";
136 // like regular jaunt, but expects a list of vectors as the first parameter; this list
137 // should be in the jaunter notecard format (separated by pipe characters).
138 // the second parameter, if any, should be 1 for forwards traversal and 0 for backwards.
142 //requires menutini library v4.2 or better.
144 // do not redefine these constants.
145 integer MENUTINI_HUFFWARE_ID = 10009;
146 // the unique id within the huffware system for the jaunt script to
147 // accept commands on. this is used in llMessageLinked as the num parameter.
148 // commands available via the menu system:
149 string SHOW_MENU_COMMAND = "#menu#";
150 // the command that tells menutini to show a menu defined by parameters
151 // that are passed along. these must be: the menu name, the menu's title
152 // (which is really the info to show as content in the main box of the menu),
153 // the wrapped list of commands to show as menu buttons, the menu system
154 // channel's for listening, and the key to listen to.
155 // the reply will include: the menu name, the choice made and the key for
163 llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, FULL_STOP_COMMAND, "");
166 request_jaunt_up(integer distance)
168 llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, JAUNT_UP_COMMAND, (string)distance);
171 // global variables...
174 integer current_state;
175 vector home_position; // the location where the pet lives.
177 integer pending_target = FALSE; // is a target still being sought.
179 integer _COMMAND_CHANNEL;
180 string _COMMAND_MESSAGE = "How may I assist you?";
183 vector TARGET_POSITION;
185 float _FREE_RANGE = 10.0;
187 string SIT_TEXT = "Meditate";
189 string SIT_ANIMATION = "yoga_float";
191 vector SIT_POSITION = <0.2, 0.2, 0.4>;
193 vector SIT_ROTATION = <0.0, 0.0, 0.0>;
195 key SITTING_AVATAR_KEY = NULL_KEY;
197 // options for follow menu that pops up when pet is told to follow someone.
199 list _FOLLOW_NAME; // filled in with nearby avatars.
200 integer _FOLLOW_CHANNEL;
201 string _FOLLOW_MESSAGE = "Who should I follow?";
203 integer seeking_avatars = FALSE;
210 // locates the string "text" in the list to "search_in".
211 integer find_in_list(list search_in, string text)
213 integer len = llGetListLength(search_in);
215 for (i = 0; i < len; i++) {
216 if (llList2String(search_in, i) == text)
224 integer debug_num = 0;
226 // a debugging output method. can be disabled entirely in one place.
227 log_it(string to_say)
230 // tell this to the owner.
231 llOwnerSay((string)debug_num + "- " + to_say);
232 // say this on open chat, but use an unusual channel.
233 // llSay(108, (string)debug_num + "- " + to_say);
240 // returns a string version of the state 's'.
241 string name_for_state(integer s) {
242 if (s == STATE_STAY) return "stay";
243 if (s == STATE_FREE) return "go home";
244 if (s == STATE_FOLLOW) return "follow";
245 if (s == STATE_COME) return "come";
246 if (s == STATE_WANDER) return "wander";
247 if (s == STATE_ATTACK) return "attack";
253 list current_buttons; // holds onto the set of menu options.
255 integer random_channel() { return -(integer)(llFrand(40000) + 20000); }
257 string stringize_list(list to_flatten) {
258 return llDumpList2String(to_flatten, HUFFWARE_ITEM_SEPARATOR);
261 // pops up a menu to interact with the pet's owner.
262 show_menu(string menu_name, string title, list buttons, integer channel)
264 current_buttons = buttons;
265 key listen_to = _OWNER;
266 llMessageLinked(LINK_THIS, MENUTINI_HUFFWARE_ID, SHOW_MENU_COMMAND,
267 menu_name + HUFFWARE_PARM_SEPARATOR
268 + title + HUFFWARE_PARM_SEPARATOR + stringize_list(current_buttons)
269 + HUFFWARE_PARM_SEPARATOR + (string)channel
270 + HUFFWARE_PARM_SEPARATOR + (string)listen_to);
273 // causes a state change to make the pet stay.
276 current_state = STATE_STAY;
281 // handle the response message when the user chooses a button.
282 react_to_menu(integer sender, integer num, string msg, key id)
284 log_it("react menu: " + msg + " parm=" + (string)id);
285 list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
286 string menu_name = llList2String(parms, 0);
287 string choice = llList2String(parms, 1);
289 if ( (num != MENUTINI_HUFFWARE_ID + REPLY_DISTANCE) || (msg != SHOW_MENU_COMMAND) ) {
290 log_it("why here in react to menu, not for us?");
294 if (menu_name == PET_MENU_NAME) {
295 log_it("react: snd=" + (string)sender + " num=" + (string)num + " msg=" + msg + " key=" + (string)id);
296 if (find_in_list(SUPPORTED_COMMANDS, choice) < 0) {
297 llOwnerSay("i don't know what you mean...");
300 llOwnerSay("i heard you tell me: menu=" + menu_name + " choice=" + choice);
302 // handle commands that are major changes in state...
304 if (choice == "come") {
305 llOwnerSay("coming to find you now...");
306 current_state = STATE_COME;
307 llSetPrimitiveParams([PRIM_PHYSICS, TRUE,
309 PRIM_PHANTOM, TRUE]);
311 llSensorRepeat("", _OWNER, AGENT, TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL);
314 if (choice == "stay") {
315 llOwnerSay("i will stay right here...");
319 if (choice == "wander") {
320 current_state = STATE_WANDER;
321 llOwnerSay("i'm going to wander around and kind of vaguely follow you...");
322 llSetPrimitiveParams([PRIM_PHYSICS, TRUE,
324 PRIM_PHANTOM, TRUE]);
326 llSensorRepeat("", _OWNER, AGENT, TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL);
329 if ( (choice == "follow") || (choice == "attack") ) {
330 seeking_avatars = TRUE;
333 if (choice == "attack") {
334 // we only attack avatars.
335 //or not. since that's boring. watching a pet attack a physical object is fun.
336 llSensor("", NULL_KEY, AGENT | ACTIVE, TARGETING_SENSOR_RANGE, PI);
337 current_state = STATE_ATTACK;
339 // look for both objects and avatars to follow.
340 llSensor("", NULL_KEY, AGENT | ACTIVE, TARGETING_SENSOR_RANGE, PI);
341 current_state = STATE_FOLLOW;
345 if (choice == "go home") {
346 current_state = STATE_FREE; // free to roam about the cabin, or wherever home is.
347 llOwnerSay("i'm going home now.");
348 jaunt_to_location(home_position);
351 // commands that don't lead to state changes...
353 if (choice == "status") {
354 string seek_addition;
355 if (KEY_OF_TARGET != "")
356 seek_addition = "was last seeking " + llKey2Name(KEY_OF_TARGET);
357 llOwnerSay("my name is " + llGetObjectName() + " and state is '"
358 + name_for_state(current_state) + "'.\n"
362 if (choice == "set home") {
363 list pos_list = llGetObjectDetails(llGetOwner(), [OBJECT_POS]);
364 home_position = llList2Vector(pos_list, 0);
365 llOwnerSay("i'm setting my home to " + (string)home_position);
366 //hmmm: use a rounding print to show the position.
368 if (choice == "set name") {
369 llOwnerSay("to change my name from " + llGetObjectName() + ",\ntell me my new name by typing:\n/" + (string)PET_CHAT_CHANNEL + " name My Cool New Name");
372 } else if (menu_name == PET_REPLY_MENU) {
373 log_it("menu-act follow: snd=" + (string)sender + " num=" + (string)num + " msg=" + msg + " id=" + (string)id);
374 llSetPrimitiveParams([PRIM_PHYSICS, TRUE,
376 PRIM_PHANTOM, !(current_state == STATE_ATTACK)]);
377 integer choice_indy = find_in_list(_FOLLOW_NAME, choice);
378 if (choice_indy < 0) {
379 //log_it("choice was not found in list");
380 //log_it("followname list is: " + (string)_FOLLOW_NAME);
382 string action = "follow";
383 if (current_state == STATE_ATTACK) action = "attack";
384 llOwnerSay("now " + action + "ing "
385 + llList2String(_FOLLOW_NAME, choice_indy) + "...");
386 seeking_avatars = FALSE;
387 KEY_OF_TARGET = llList2Key(_FOLLOW_KEY, choice_indy);
389 llSensorRepeat("", KEY_OF_TARGET, AGENT | ACTIVE,
390 TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL);
395 // processes the hits that we get back from the sensor. the information we receive
396 // is needed for most of the pet states.
397 handle_sensor(integer num_detected)
399 if (current_state == STATE_COME) {
400 go_to_target(_OWNER, llDetectedPos(0));
404 if ( (current_state == STATE_FOLLOW) || (current_state == STATE_ATTACK) ) {
405 if (seeking_avatars) {
406 // reset the list of keys and names that were found previously.
409 // show the full set found if it will fit, otherwise just 12.
410 integer num_to_show = num_detected;
411 if (num_to_show > 12) num_to_show = 12;
412 // examine each of the avatars found and put them on the list.
414 for (i = 0 ; i < num_to_show; i++) {
415 key to_follow = llDetectedKey(i);
416 if (to_follow != NULL_KEY) {
417 _FOLLOW_KEY += [to_follow];
418 string str = llDetectedName(i);
419 // trim the menu item if it has hit the maximum limit.
420 if (llStringLength(str) > 24) str = llGetSubString(str, 0, 23);
421 integer name_try = 0;
422 while (find_in_list(_FOLLOW_NAME, str) >= 0) {
423 // this guy is already listed under that name, so change it a bit.
424 str = llGetSubString(str, 0, 22) + (string)name_try++;
426 _FOLLOW_NAME += [str];
429 // now ask who to follow.
430 if (llGetListLength(_FOLLOW_KEY)) {
431 show_menu(PET_REPLY_MENU, _FOLLOW_MESSAGE, _FOLLOW_NAME, _FOLLOW_CHANNEL);
434 // not seeking the avatar any more; follow who was chosen.
435 go_to_target(KEY_OF_TARGET, llDetectedPos(0));
440 if (current_state == STATE_WANDER) {
441 if (jaunt_responses_awaited) return; // skip doing anything while we're still waiting.
442 vector pos = llDetectedPos(0);
443 float omg = llFrand(1) * PI * 2;
444 float t_r = llFrand(1) * _FREE_RANGE;
445 float t_x = t_r * llCos(omg);
446 float t_y = t_r * llSin(omg);
447 go_to_target(NULL_KEY, pos + <t_x, t_y, 0.0>);
454 if (current_state != STATE_STAY) {
455 // make sure a bad jaunt didn't break our physics.
456 llSetStatus(STATUS_PHYSICS, TRUE);
459 if (jaunt_responses_awaited) {
460 // we are not quite there yet.
461 if (slackness_counter++ > MAXIMUM_SLACKNESS) {
462 // go back to the main state. we took too long.
463 log_it("waiting for jaunt timed out.");
464 ///argh? jaunt_responses_awaited--;
465 slackness_counter = 0;
466 } else return; // not time yet for rest of timed actions.
469 // handle the free state, since we need may to readjust the target.
470 if (current_state == STATE_FREE) {
471 if (pending_target) return; // haven't arrived at previous yet.
472 vector pos = home_position;
473 float omg = llFrand(1) * PI * 2;
474 //hmmm: make free range settable
475 float t_r = llFrand(1) * _FREE_RANGE;
476 float t_x = t_r * llCos(omg);
477 float t_y = t_r * llSin(omg);
478 go_to_target(NULL_KEY, pos + <t_x, t_y, 0.0>);
483 handle_hearing_voices(integer channel, string name, key id, string message)
485 if (channel != PET_CHAT_CHANNEL) return; // not our channel.
486 //log_it("into handle voice, msg=" + message);
487 if (id != llGetOwner()) return; // not authorized.
488 // we found a command. which specific one?
489 if (is_prefix(message, "up")) {
492 string dist = llDeleteSubString(message, 0, 2);
493 if (dist == "") dist = (string)DEFAULT_BUMP_SIZE;
494 request_jaunt_up((integer)dist);
495 llOwnerSay("bumping up by " + dist);
496 } else if (is_prefix(message, "down")) {
499 string dist = llDeleteSubString(message, 0, 4);
500 if (dist == "") dist = (string)DEFAULT_BUMP_SIZE;
501 request_jaunt_up(-(integer)dist);
502 llOwnerSay("bumping down by " + dist);
503 } else if (is_prefix(message, "jaunt")) {
504 // zip to a specific place in the sim.
506 string where = llDeleteSubString(message, 0, 5);
508 llOwnerSay("i can't jaunt to like nowhere dude.");
511 vector loc = (vector)where;
512 if (loc == <0.0, 0.0, 0.0>) {
513 llOwnerSay("jaunt locations should be in the vector <x, y, z> format, and jaunting to <0, 0, 0> is unsupported.");
516 llOwnerSay("jaunting to " + (string)loc);
517 jaunt_to_location(loc);
518 } else if (is_prefix(message, "name")) {
519 // toss the command portion to get our new name.
520 string new_name = llDeleteSubString(message, 0, 4);
521 if (llStringLength(new_name) > 0) {
522 llOwnerSay("wheeee! my new name is: " + new_name);
523 llSetObjectName(new_name);
526 // no data was given for the name.
527 llOwnerSay("my name is still " + llGetObjectName());
530 // we support a simple translation for a few orders.
531 if (message == "free") message = "go home";
533 // see if we can just flip this into a menu command instead. we don't
534 // really care whether that works or not, since anything that doesn't work is
536 llMessageLinked(LINK_THIS, _COMMAND_CHANNEL, SHOW_MENU_COMMAND,
537 PET_MENU_NAME + HUFFWARE_PARM_SEPARATOR + message);
545 //log_it("stopping pet from moving...");
546 llSetPrimitiveParams([PRIM_PHYSICS, FALSE,
548 PRIM_PHANTOM, TRUE]);
551 go_to_target(key av, vector pos)
553 //log_it("told to go to target: key=" + (string)av + " pos=" + (string)pos);
554 TARGET_POSITION = pos;
555 if (av != NULL_KEY) {
556 vector av_size = llGetAgentSize(av);
558 // if it's an object, use a different method to find its height.
559 if (av_size.z == 0.0) {
560 // use the object's height.
561 list box = llGetBoundingBox(KEY_OF_TARGET);
562 float object_height = llVecDist(llList2Vector(box, 0), llList2Vector(box, 1));
563 av_size.z = object_height;
565 // adding to get pet above target.
566 TARGET_POSITION += < 0.0, 0.0, av_size.z / 2.0>;
567 //log_it("adjusted targposn: " + (string)TARGET_POSITION);
569 if (current_state == STATE_ATTACK) {
570 TARGET_POSITION += < 0.0, 0.0, ATTACK_HEIGHT_ABOVE_FOLLOWED_OBJECT>;
571 TARGET_POSITION += <llFrand(2) * ATTACK_X_MARGIN - ATTACK_X_MARGIN,
572 llFrand(2) * ATTACK_Y_MARGIN - ATTACK_Y_MARGIN,
573 llFrand(2) * ATTACK_Z_MARGIN - ATTACK_Z_MARGIN>;
575 //log_it("normal target calc");
576 TARGET_POSITION += < 0.0, 0.0, DEFAULT_HEIGHT_ABOVE_FOLLOWED_OBJECT>;
577 TARGET_POSITION += <llFrand(2) * DEFAULT_X_MARGIN - DEFAULT_X_MARGIN,
578 llFrand(2) * DEFAULT_Y_MARGIN - DEFAULT_Y_MARGIN,
579 llFrand(2) * DEFAULT_Z_MARGIN - DEFAULT_Z_MARGIN>;
581 // trim the height a bit to keep the pet on-world.
582 if (TARGET_POSITION.z > 4095.0)
583 TARGET_POSITION.z = 4095.0;
586 integer jaunt_responses_awaited = 0;
587 // the number of pending jumps that we are hoping will happen.
589 integer slackness_counter;
590 // how many snoozes we've had waiting for our destination.
592 jaunt_to_location(vector target)
594 // send jaunt request to get us to the specified place.
595 llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, JAUNT_COMMAND, (string)target);
596 // add one to our counter so we know a jaunt is in progress.
597 jaunt_responses_awaited++;
598 // reset the overflow counter to recognize a new jaunt.
599 slackness_counter = 0;
602 vector previous_position;
603 // how far away target was last time.
607 // first, let's get into the right state of existence.
608 llSetStatus(STATUS_PHYSICS, TRUE); // we need to be able to move around here.
609 if (current_state == STATE_ATTACK) {
610 llSetStatus(STATUS_PHANTOM, FALSE); // we can bonk into things now.
612 llSetStatus(STATUS_PHANTOM, TRUE); // there are no obstructive contacts.
615 vector current_pos = llGetPos();
616 float distance = llVecDist(TARGET_POSITION, current_pos);
617 // a simple linear velocity calculation based on the object's distance.
619 if (current_state == STATE_ATTACK) {
620 velocity = ATTACK_BASE_VELOCITY + VELOCITY_MULTIPLIER * (distance / 10.0);
621 // beef the velocity up for attack mode.
624 velocity = DEFAULT_BASE_VELOCITY + VELOCITY_MULTIPLIER * (distance / 10.0);
627 //hmmm: make that 20 a constant
628 integer jump_regardless = FALSE;
629 if (llVecDist(current_pos, previous_position) >= 20) {
630 // we will always re-target when the distances have changed that much; this could mean
631 // the avatar is falling away.
632 jump_regardless = TRUE;
634 float IN_RANGE_CHANCE_TO_BAIL = 0.9;
635 float ATTACK_IN_RANGE_CHANCE_TO_BAIL = 0.5;
637 float NEAR_RANGE_CHANCE_TO_BAIL = 0.5;
638 float ATTACK_NEAR_RANGE_CHANCE_TO_BAIL = 0.1;
640 if (distance <= MAXIMUM_TARGETING_DISTANCE) {
641 // damp out the equation if the target is close enough.
642 if (current_state == STATE_ATTACK) velocity = ATTACK_BASE_VELOCITY;
643 else velocity = DEFAULT_BASE_VELOCITY;
644 float within_range_chance = IN_RANGE_CHANCE_TO_BAIL;
645 if (current_state == STATE_ATTACK) within_range_chance = ATTACK_IN_RANGE_CHANCE_TO_BAIL;
646 if (llFrand(1.0) <= within_range_chance) return; // do nothing; close enough.
647 } else if (distance <= 2.0 * MAXIMUM_TARGETING_DISTANCE) {
648 // we have a bit larger chance of setting a new target if the
649 // distance is pretty close still.
650 float near_range_chance = NEAR_RANGE_CHANCE_TO_BAIL;
651 if (current_state == STATE_ATTACK) near_range_chance = ATTACK_NEAR_RANGE_CHANCE_TO_BAIL;
652 if (llFrand(1.0) <= near_range_chance) return;
654 previous_position = current_pos;
655 //log_it("dist=" + (string)distance + " vel=" + (string)velocity);
656 float time = distance / velocity;
657 _TARGET_ID = llTarget(TARGET_POSITION, MAXIMUM_TARGETING_DISTANCE);
658 pending_target = TRUE;
659 // make sure we're in a physics mode before attempting physics changes...
660 llSetStatus(STATUS_PHYSICS, TRUE);
661 if (SITTING_AVATAR_KEY == NULL_KEY) {
662 // when we have nobody riding, we can look wherever we want.
663 llLookAt(TARGET_POSITION, 0.7, 0.5);
665 // if we're holding onto an avatar, we keep them pointed in a reasonable way.
666 vector curr_pos = llGetPos();
667 vector new_lookat = <curr_pos.x, curr_pos.y, curr_pos.z + 1>;
668 llLookAt(new_lookat, 0.7, 0.5);
670 //log_it("setting move to target: " + (string)TARGET_POSITION);
671 llMoveToTarget(TARGET_POSITION, time);
673 integer panic_dist = DEFAULT_PANIC_DISTANCE;
674 if (current_state == STATE_ATTACK) {
675 panic_dist = ATTACK_PANIC_DISTANCE;
678 // don't try to jump if we're still awaiting a jump response.
679 if (!jaunt_responses_awaited && (distance > panic_dist) ) {
680 // we need to shorten the distance to our buddy now.
681 jaunt_to_location(TARGET_POSITION);
682 } else if (jump_regardless || (distance > TARGETING_SENSOR_RANGE - 10)) {
683 // we are double our panic point, so jump even if still waiting for a reply.
684 // however, we don't want to queue up too many jaunts at a time either.
685 if (jaunt_responses_awaited <= 2) {
686 jaunt_to_location(TARGET_POSITION);
690 // push the attack target if we're close enough.
691 if ( (current_state == STATE_ATTACK) && (distance < ATTACK_PUSH_DIST_THRESHOLD) ) {
692 // only decide to push if they win the lottery here.
693 if (llFrand(1.0) < ATTACK_PUSH_CHANCE) {
694 llPushObject(KEY_OF_TARGET, ATTACK_PUSH_MAGNITUDE * llRot2Up(llGetRot()), ZERO_VECTOR, FALSE);
702 llSetText(llGetObjectName(), <0.6, 0.3, 0.8>, 1.0);
705 // processes a link message from some other script.
706 handle_link_message(integer which, integer num, string msg, key id)
708 //log_it("got msg=" + msg + " id=" + (string)id);
709 if (num == JAUNT_HUFFWARE_ID + REPLY_DISTANCE) {
710 //log_it("link jaunt reply");
711 if (msg == JAUNT_COMMAND) {
712 jaunt_responses_awaited--; // one less response being awaited.
713 if (jaunt_responses_awaited < 0) {
714 log_it("erroneously went below zero for jaunt responses!");
715 jaunt_responses_awaited = 0;
718 list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
719 integer last_jaunt_was_success = (integer)llList2String(parms, 0);
720 vector posn = (vector)llList2String(parms, 1);
721 //log_it("got a reply for a jaunt request, success=" + (string)last_jaunt_was_success + " posn=" + (string)posn);
725 if (num != MENUTINI_HUFFWARE_ID + REPLY_DISTANCE) return; // not for us.
726 //log_it("menu reply");
727 react_to_menu(which, num, msg, id);
730 // returns TRUE if the "prefix" string is the first part of "compare_with".
731 integer is_prefix(string compare_with, string prefix)
732 { return (llSubStringIndex(compare_with, prefix) == 0); }
735 // huffware script: auto-retire, by fred huffhines, version 2.5.
736 // distributed under BSD-like license.
737 // !! keep in mind that this code must be *copied* into another
738 // !! script that you wish to add auto-retirement capability to.
739 // when a script has auto_retire in it, it can be dropped into an
740 // object and the most recent version of the script will destroy
741 // all older versions.
743 // the version numbers are embedded into the script names themselves.
744 // the notation for versions uses a letter 'v', followed by two numbers
745 // in the form "major.minor".
746 // major and minor versions are implicitly considered as a floating point
747 // number that increases with each newer version of the script. thus,
748 // "hazmap v0.1" might be the first script in the "hazmap" script continuum,
749 // and "hazmap v3.2" is a more recent version.
751 // example usage of the auto-retirement script:
754 // auto_retire(); // make sure newest addition is only version of script.
757 // this script is partly based on the self-upgrading scripts from markov brodsky
758 // and jippen faddoul.
761 string self = llGetScriptName(); // the name of this script.
762 list split = compute_basename_and_version(self);
763 if (llGetListLength(split) != 2) return; // nothing to do for this script.
764 string basename = llList2String(split, 0); // script name with no version attached.
765 string version_string = llList2String(split, 1); // the version found.
767 // find any scripts that match the basename. they are variants of this script.
768 for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
769 //log_it("invpo=" + (string)posn);
770 string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
771 if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
772 // found a basic match at least.
773 list inv_split = compute_basename_and_version(curr_script);
774 if (llGetListLength(inv_split) == 2) {
775 // see if this script is more ancient.
776 string inv_version_string = llList2String(inv_split, 1); // the version found.
777 // must make sure that the retiring script is completely the identical basename;
778 // just matching in the front doesn't make it a relative.
779 if ( (llList2String(inv_split, 0) == basename)
780 && ((float)inv_version_string < (float)version_string) ) {
781 // remove script with same name from inventory that has inferior version.
782 llRemoveInventory(curr_script);
789 // separates the base script name and version number. used by auto_retire.
790 list compute_basename_and_version(string to_chop_up)
792 // minimum script name is 2 characters plus a version.
793 integer space_v_posn;
794 // find the last useful space and 'v' combo.
795 for (space_v_posn = llStringLength(to_chop_up) - 3;
796 (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
798 // look for space and v but do nothing else.
799 //log_it("pos=" + (string)space_v_posn);
801 if (space_v_posn < 2) return []; // no space found.
802 //log_it("space v@" + (string)space_v_posn);
803 // now we zoom through the stuff after our beloved v character and find any evil
804 // space characters, which are most likely from SL having found a duplicate item
805 // name and not so helpfully renamed it for us.
807 for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
808 //log_it("indy=" + (string)space_v_posn);
809 if (llGetSubString(to_chop_up, indy, indy) == " ") {
810 // found one; zap it. since we're going backwards we don't need to
811 // adjust the loop at all.
812 to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
813 //log_it("saw case of previously redundant item, aieee. flattened: " + to_chop_up);
816 string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
817 // ditch the space character for our numerical check.
818 string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
819 // strip out a 'v' if there is one.
820 if (llGetSubString(chop_suffix, 0, 0) == "v")
821 chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
822 // if valid floating point number and greater than zero, that works for our version.
823 string basename = to_chop_up; // script name with no version attached.
824 if ((float)chop_suffix > 0.0) {
825 // this is a big success right here.
826 basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
827 return [ basename, chop_suffix ];
829 // seems like we found nothing useful.
838 llSetPrimitiveParams([PRIM_PHYSICS, FALSE,
840 PRIM_PHANTOM, TRUE]);
841 llSitTarget(SIT_POSITION, llEuler2Rot(SIT_ROTATION * DEG_TO_RAD));
842 llSetSitText(SIT_TEXT);
844 _OWNER = llGetOwner();
847 current_state = STATE_FREE;
848 TARGET_POSITION = llGetPos();
849 llSetTimerEvent(PERIODIC_INTERVAL);
850 slackness_counter = 0;
851 _COMMAND_CHANNEL = random_channel();
852 _FOLLOW_CHANNEL = random_channel();
853 _COMMAND_CHANNEL = random_channel();
854 llListen(PET_CHAT_CHANNEL, "", llGetOwner(), "");
855 home_position = llGetPos(); // start in a known place.
860 state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
861 on_rez(integer parm) { state rerun; }
863 state rerun { state_entry() { state default; } }
872 on_rez(integer param) { llResetScript(); }
874 touch_start(integer num_detected) {
876 //change title to show menuing state?
877 if (_OWNER == llDetectedKey(0)) {
878 // show our menu here.
879 show_menu(PET_MENU_NAME, _COMMAND_MESSAGE, SUPPORTED_COMMANDS, _COMMAND_CHANNEL);
883 link_message(integer sender, integer num, string msg, key id) {
884 handle_link_message(sender, num, msg, id);
887 sensor(integer num_detected) {
888 //log_it("sensor found " + llDetectedName(0));
889 handle_sensor(num_detected);
893 //use another means to find the avatar?
896 at_target(integer number, vector targetpos, vector ourpos) {
897 //log_it("at target");
898 llTargetRemove(_TARGET_ID);
899 pending_target = FALSE;
900 llStopMoveToTarget();
904 //log_it("not at target");
908 changed(integer change) {
909 if (change & CHANGED_LINK) {
910 key av = llAvatarOnSitTarget();
911 if (SITTING_AVATAR_KEY != NULL_KEY) {
912 if (av == NULL_KEY) {
913 llStopAnimation(SIT_ANIMATION);
914 SITTING_AVATAR_KEY = NULL_KEY;
917 if (av != NULL_KEY) {
918 SITTING_AVATAR_KEY = av;
919 llRequestPermissions(SITTING_AVATAR_KEY, PERMISSION_TRIGGER_ANIMATION);
920 // we wish we could make the avatar a phantom here, but that's not allowed.
926 run_time_permissions(integer perm) {
927 key perm_key = llGetPermissionsKey();
928 if (perm_key == SITTING_AVATAR_KEY) {
929 if (perm & PERMISSION_TRIGGER_ANIMATION) {
930 list anms = llGetAnimationList(SITTING_AVATAR_KEY);
932 for (i = 0 ; i < llGetListLength(anms) ; i++) {
933 llStopAnimation(llList2Key(anms, i));
935 llStartAnimation(SIT_ANIMATION);
944 listen(integer channel, string name, key id, string message) {
945 handle_hearing_voices(channel, name, id, message);
951 // this script is based (a lot!) on the "pet" script that might
952 // have been written by kazumasa loon. there was no attribution
953 // of author in the script, but the creator was kazumasa. thanks dude!
955 // that being said, the script was redone a lot by fred huffhines,
956 // mainly in the following areas:
958 // march or april 2008: added teleport capability to script. pet will now attempt
959 // to keep up with the owner during follow mode by teleporting to her.
961 // may 2008: added ipc menu system. now menus are dealt with by the huffware
962 // menu system, removing a lot of code from this script.