normalized perms on all files, to avoid relying on any stored executable bits in...
[feisty_meow.git] / huffware / huffotronic_scripts / huff-pet_v18.6.txt
1 
2 // huffware script: huff-pet, by fred huffhines
3 //
4 // this is yet another implementation of a pet script in LSL.
5 //
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.
8 //
9
10 //to-do zone:
11
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!
16
17 // end to-do zone.
18
19 // constants for the pet that one might want to change.
20
21 integer PET_CHAT_CHANNEL = 28;
22     // the channel on which the pet will listen for commands from the owner.
23
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.    
31
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.
36
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.
41
42 integer DEFAULT_BUMP_SIZE = 48;
43     // the default size in meters of the displacement caused by up, down, etc commands.
44
45 // the margin values below are the range of motion that the pet is allowed
46 // on each axis.
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;
54
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.
58 //is that right?
59
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.
66
67 // other constants that are more advanced and should generally not change...
68
69 float TARGETING_SENSOR_RANGE = 96.0;
70     // the maximum distance the pet will try to see the target at.
71     
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.
75
76 float PERIODIC_INTERVAL = 0.42;
77     // how frequently our timer event fires.
78
79 integer MAXIMUM_SLACKNESS = 28;
80     // how many timer hits we'll allow before reverting to the default state.
81
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.
85
86 string PET_MENU_NAME = "#woof";  // name for our menu.
87 string PET_REPLY_MENU = "#aroo";  // replies with data.
88
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;
96
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.
108
109 // requires: jaunting library v3.4 or higher.
110 //////////////
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.
122 //////////////
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.
139 //
140 //////////////
141
142 //requires menutini library v4.2 or better.
143 //////////////
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
156     // the avatar.
157 //
158 //////////////
159
160
161 request_full_stop()
162 {
163     llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, FULL_STOP_COMMAND, "");
164 }
165
166 request_jaunt_up(integer distance)
167 {
168     llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, JAUNT_UP_COMMAND, (string)distance);
169 }
170
171 // global variables...
172
173 key _OWNER;
174 integer current_state;
175 vector home_position;  // the location where the pet lives.
176
177 integer pending_target = FALSE;  // is a target still being sought.
178
179 integer  _COMMAND_CHANNEL;
180 string   _COMMAND_MESSAGE   = "How may I assist you?";
181
182 integer  _TARGET_ID;
183 vector TARGET_POSITION;
184
185 float    _FREE_RANGE        =  10.0;
186
187 string SIT_TEXT = "Meditate";
188
189 string SIT_ANIMATION = "yoga_float";
190
191 vector SIT_POSITION = <0.2, 0.2, 0.4>;
192
193 vector SIT_ROTATION = <0.0, 0.0, 0.0>;
194
195 key      SITTING_AVATAR_KEY           = NULL_KEY; 
196
197 // options for follow menu that pops up when pet is told to follow someone.
198 list     _FOLLOW_KEY;
199 list     _FOLLOW_NAME;  // filled in with nearby avatars.
200 integer  _FOLLOW_CHANNEL;
201 string   _FOLLOW_MESSAGE = "Who should I follow?";
202
203 integer  seeking_avatars         = FALSE;
204 key      KEY_OF_TARGET;
205
206 //////////////
207
208 // from hufflets...
209
210 // locates the string "text" in the list to "search_in".
211 integer find_in_list(list search_in, string text)
212
213     integer len = llGetListLength(search_in);
214     integer i; 
215     for (i = 0; i < len; i++) { 
216         if (llList2String(search_in, i) == text) 
217             return i; 
218     }
219     return -1;
220 }
221
222 ///////////////
223
224 integer debug_num = 0;
225
226 // a debugging output method.  can be disabled entirely in one place.
227 log_it(string to_say)
228 {
229     debug_num++;
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);
234 }
235
236 ///////////////
237
238 // info...
239
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";
248     return "unknown";
249 }
250
251 // menu methods...
252
253 list current_buttons;  // holds onto the set of menu options.
254
255 integer random_channel() { return -(integer)(llFrand(40000) + 20000); }
256
257 string stringize_list(list to_flatten) {
258     return llDumpList2String(to_flatten, HUFFWARE_ITEM_SEPARATOR);
259 }
260
261 // pops up a menu to interact with the pet's owner.
262 show_menu(string menu_name, string title, list buttons, integer channel)
263 {
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);
271 }
272
273 // causes a state change to make the pet stay.
274 enter_stay_state()
275 {
276     current_state = STATE_STAY;
277     llSensorRemove();
278     stop_pet();
279 }
280
281 // handle the response message when the user chooses a button.
282 react_to_menu(integer sender, integer num, string msg, key id)
283 {
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);
288
289     if ( (num != MENUTINI_HUFFWARE_ID + REPLY_DISTANCE) || (msg != SHOW_MENU_COMMAND) ) {
290 log_it("why here in react to menu, not for us?");
291         return;
292     }
293
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...");
298             return;
299         }
300 llOwnerSay("i heard you tell me: menu=" + menu_name + " choice=" + choice);
301     
302         // handle commands that are major changes in state...
303     
304         if (choice == "come") {
305             llOwnerSay("coming to find you now...");
306             current_state = STATE_COME;
307             llSetPrimitiveParams([PRIM_PHYSICS, TRUE,
308             //]);
309             PRIM_PHANTOM, TRUE]);
310             llSensorRemove();
311             llSensorRepeat("", _OWNER, AGENT, TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL);
312         }
313         
314         if (choice == "stay") {
315             llOwnerSay("i will stay right here...");
316             enter_stay_state();
317         }
318         
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,
323             //]);
324              PRIM_PHANTOM, TRUE]);
325             llSensorRemove();
326             llSensorRepeat("", _OWNER, AGENT, TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL);
327         }
328         
329         if ( (choice == "follow") || (choice == "attack") ) {
330             seeking_avatars = TRUE;
331             stop_pet();
332             llSensorRemove();
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;
338             } else {
339                 // look for both objects and avatars to follow.
340                 llSensor("", NULL_KEY, AGENT | ACTIVE, TARGETING_SENSOR_RANGE, PI);
341                 current_state = STATE_FOLLOW;
342             }
343         }
344         
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);
349         }
350     
351         // commands that don't lead to state changes...
352     
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"
359                 + seek_addition);
360         }
361     
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.
367         }
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");
370         }
371     
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,
375         //]);
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);
381         } else {
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);
388             llSensorRemove();
389             llSensorRepeat("", KEY_OF_TARGET, AGENT | ACTIVE,
390                 TARGETING_SENSOR_RANGE, PI, SENSOR_INTERVAL);  
391         }
392     }
393 }
394
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)
398 {
399     if (current_state == STATE_COME) {
400         go_to_target(_OWNER, llDetectedPos(0));
401         motivate();
402     }
403     
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.
407             _FOLLOW_KEY  = [];
408             _FOLLOW_NAME = [];
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.
413             integer i;
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++;
425                     }
426                     _FOLLOW_NAME += [str];
427                 }
428             }
429             // now ask who to follow.
430             if (llGetListLength(_FOLLOW_KEY)) {
431                 show_menu(PET_REPLY_MENU, _FOLLOW_MESSAGE, _FOLLOW_NAME, _FOLLOW_CHANNEL);
432             }
433         } else {
434             // not seeking the avatar any more; follow who was chosen.
435             go_to_target(KEY_OF_TARGET, llDetectedPos(0));
436             motivate();
437         }
438     }
439
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>);
448         motivate();
449     }
450
451 }
452
453 handle_timer() {
454     if (current_state != STATE_STAY) {
455         // make sure a bad jaunt didn't break our physics.
456         llSetStatus(STATUS_PHYSICS, TRUE);
457     }
458
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.
467     }
468     
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>);
479         motivate();
480     }
481 }
482
483 handle_hearing_voices(integer channel, string name, key id, string message)
484 {
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")) {
490         // upwards bump.
491         enter_stay_state();
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")) {
497         // downwards bump.
498         enter_stay_state();
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.
505         enter_stay_state();
506         string where = llDeleteSubString(message, 0, 5);
507         if (where == "") {
508             llOwnerSay("i can't jaunt to like nowhere dude.");
509             return;
510         }
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.");
514             return;
515         }
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);
524             show_title();
525         } else {
526             // no data was given for the name.
527             llOwnerSay("my name is still " + llGetObjectName());
528         }
529     } else {
530         // we support a simple translation for a few orders.
531         if (message == "free") message = "go home";
532         
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
535         // a bogus command.
536         llMessageLinked(LINK_THIS, _COMMAND_CHANNEL, SHOW_MENU_COMMAND,
537             PET_MENU_NAME + HUFFWARE_PARM_SEPARATOR + message);
538     }
539 }
540
541 //////////////
542
543 stop_pet()
544 {
545 //log_it("stopping pet from moving...");
546     llSetPrimitiveParams([PRIM_PHYSICS, FALSE,
547     //]);
548     PRIM_PHANTOM, TRUE]);
549 }
550
551 go_to_target(key av, vector pos)
552 {
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);
557
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;
564         }
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);
568     }
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>;
574     } else {
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>;
580     }
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;
584 }
585
586 integer jaunt_responses_awaited = 0;
587     // the number of pending jumps that we are hoping will happen.
588
589 integer slackness_counter;
590     // how many snoozes we've had waiting for our destination.
591
592 jaunt_to_location(vector target)
593 {
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;
600 }
601
602 vector previous_position;
603     // how far away target was last time.
604
605 motivate()
606 {
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.
611     } else {
612         llSetStatus(STATUS_PHANTOM, TRUE);  // there are no obstructive contacts.
613     }
614
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.
618     float velocity;
619     if (current_state == STATE_ATTACK) {
620         velocity = ATTACK_BASE_VELOCITY + VELOCITY_MULTIPLIER * (distance / 10.0);
621         // beef the velocity up for attack mode.
622         velocity *= 10.0;
623     } else {
624         velocity = DEFAULT_BASE_VELOCITY + VELOCITY_MULTIPLIER * (distance / 10.0);
625     }
626
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;
633     }
634 float IN_RANGE_CHANCE_TO_BAIL = 0.9;
635 float ATTACK_IN_RANGE_CHANCE_TO_BAIL = 0.5;
636     
637 float NEAR_RANGE_CHANCE_TO_BAIL = 0.5;
638 float ATTACK_NEAR_RANGE_CHANCE_TO_BAIL = 0.1;
639
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;
653     }
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);
664     } else {
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);
669     }
670 //log_it("setting move to target: " + (string)TARGET_POSITION);
671     llMoveToTarget(TARGET_POSITION, time);
672
673     integer panic_dist = DEFAULT_PANIC_DISTANCE;
674     if (current_state == STATE_ATTACK) {
675         panic_dist = ATTACK_PANIC_DISTANCE;
676     }
677
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);
687         }
688     }
689
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);
695         }
696     }
697     
698 }
699
700 show_title()
701 {
702     llSetText(llGetObjectName(), <0.6, 0.3, 0.8>, 1.0);
703 }
704
705 // processes a link message from some other script.
706 handle_link_message(integer which, integer num, string msg, key id)
707 {
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;
716             }
717             // unpack the reply.
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);
722         }
723         return;
724     }
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);
728 }
729
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); }
733
734 //////////////
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.
742 //
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.
750 //
751 // example usage of the auto-retirement script:
752 //     default {
753 //         state_entry() {
754 //            auto_retire();  // make sure newest addition is only version of script.
755 //        }
756 //     }
757 // this script is partly based on the self-upgrading scripts from markov brodsky
758 // and jippen faddoul.
759 //////////////
760 auto_retire() {
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.
766     integer posn;
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);
783                 }
784             }
785         }
786     }
787 }
788 //
789 // separates the base script name and version number.  used by auto_retire.
790 list compute_basename_and_version(string to_chop_up)
791 {
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");
797         space_v_posn--) {
798         // look for space and v but do nothing else.
799 //log_it("pos=" + (string)space_v_posn);
800     }
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.
806     integer indy;
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);
814         }
815     }
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 ];
828     }
829     // seems like we found nothing useful.
830     return [];
831 }
832 //
833 //////////////
834
835 initialize()
836 {
837     show_title();
838     llSetPrimitiveParams([PRIM_PHYSICS, FALSE,
839     //]);
840     PRIM_PHANTOM, TRUE]);
841     llSitTarget(SIT_POSITION, llEuler2Rot(SIT_ROTATION * DEG_TO_RAD));
842     llSetSitText(SIT_TEXT);
843     llSetBuoyancy(1.0);
844     _OWNER = llGetOwner();
845     _FOLLOW_KEY = [];
846     _FOLLOW_NAME = [];
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.
856 }
857
858 default
859 {
860     state_entry() { if (llSubStringIndex(llGetObjectName(),  "huffotronic") < 0) state real_default; }
861     on_rez(integer parm) { state rerun; }
862 }
863 state rerun { state_entry() { state default; } }
864
865 state real_default
866 {
867     state_entry() {
868         auto_retire();
869         initialize();
870     }
871     
872     on_rez(integer param) { llResetScript(); }
873     
874     touch_start(integer num_detected) {
875         show_title();
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);
880         }
881     }
882
883     link_message(integer sender, integer num, string msg, key id) {
884         handle_link_message(sender, num, msg, id);
885     }
886     
887     sensor(integer num_detected) {
888 //log_it("sensor found " + llDetectedName(0));
889         handle_sensor(num_detected);
890     }
891     
892     no_sensor() {
893 //use another means to find the avatar?
894     }
895     
896     at_target(integer number, vector targetpos, vector ourpos) {
897 //log_it("at target");
898         llTargetRemove(_TARGET_ID);
899         pending_target = FALSE;
900         llStopMoveToTarget();
901     }
902     
903     not_at_target() {
904 //log_it("not at target");
905
906     }
907     
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;
915                 }
916             } else {
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.
921                 }
922             }
923         }
924     }
925
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);
931                 integer i;
932                 for (i = 0 ; i < llGetListLength(anms) ; i++) {
933                     llStopAnimation(llList2Key(anms, i));
934                 }
935                 llStartAnimation(SIT_ANIMATION);
936             }
937         }
938     }
939
940     timer() {
941         handle_timer();
942     }
943
944     listen(integer channel, string name, key id, string message) {
945         handle_hearing_voices(channel, name, id, message);
946     }
947
948 }
949
950 // attributions:
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!
954 //
955 // that being said, the script was redone a lot by fred huffhines,
956 // mainly in the following areas:
957 //
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.
960 //
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.
963