updated to also fix batch files for xsede.
[feisty_meow.git] / huffware / jaunting / jaunting_library_v15.9.lsl
1
2 // huffware script: jaunting library, by fred huffhines, released under GPL-v3 license.
3 //
4 // this script is a library of useful teleportation and movement calls.  it should be added
5 // into an object along with other scripts that use the library.  the jaunting library
6 // responds to linked messages as commands.
7 // parts of this script are based on warpPos from "Teleporter Script v 3.0 by Asira Sakai",
8 // which was found at the LSL wiki.
9 //
10 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
11 // do not use it in objects without fully realizing you are implicitly accepting that license.
12 //
13
14 // global constants...
15
16 float PROXIMITY_REQUIRED = 0.1;
17     // how close we must be to the target location to call the jaunt done.
18     // we make this fairly low accuracy since we don't want jumps in space to immediately
19     // be counted as failures.
20
21 // the most jumps the script will try to take.  the overall distance from the start
22 // to the end can be 10 * MAXIMUM_JUMPS_PER_CALL meters.
23 integer MAXIMUM_JUMPS_PER_CALL = 84;
24
25 // global variables...
26
27 vector last_posn;  // used to calculate jump distances.
28
29 // jaunting library link message API...
30 //////////////
31 // do not redefine these constants.
32 integer JAUNT_HUFFWARE_ID = 10008;
33     // the unique id within the huffware system for the jaunt script to
34     // accept commands on.  this is used in llMessageLinked as the num parameter.
35 string HUFFWARE_PARM_SEPARATOR = "{~~~}";
36     // this pattern is an uncommon thing to see in text, so we use it to separate
37     // our commands in link messages.
38 string HUFFWARE_ITEM_SEPARATOR = "{|||}";
39     // used to separate lists of items from each other when stored inside a parameter.
40     // this allows lists to be passed as single string parameters if needed.
41 integer REPLY_DISTANCE = 100008;  // offset added to service's huffware id in reply IDs.
42 // commands available via the jaunting library:
43 string JAUNT_COMMAND = "#jaunt#";
44     // command used to tell jaunt script to move object.  pass a vector with the location.
45 string FULL_STOP_COMMAND = "#fullstop#";
46     // command used to bring object to a halt.
47 string REVERSE_VELOCITY_COMMAND = "#reverse#";
48     // makes the object reverse its velocity and travel back from whence it came.
49 string SET_VELOCITY_COMMAND = "#setvelocity#";
50     // makes the velocity equal to the vector passed as the first parameter.
51 string JAUNT_UP_COMMAND = "#jauntup#";
52 string JAUNT_DOWN_COMMAND = "#jauntdown#";
53     // commands for height adjustment.  pass a float for number of meters to move.
54 string JAUNT_LIST_COMMAND = "#jauntlist#";
55     // like regular jaunt, but expects a list of vectors as the first parameter; this list
56     // should be in the jaunter notecard format (separated by pipe characters).
57     // the second parameter, if any, should be 1 for forwards traversal and 0 for backwards.
58 //////////////
59
60 // returns what we consider to be safe to do in one big jump.
61 float distance_safe_to_jaunt() { return (float)(MAXIMUM_JUMPS_PER_CALL - 4) * 10.0; }
62
63 // tests whether the destination is safe for an object to enter.
64 integer safe_for_entry(vector where)
65 {
66     if (outside_of_sim(where)) {
67         // that's an obvious wrong idea; it tends to break us.
68         return FALSE;
69     }
70     return TRUE;
71 }
72
73 // helper function for warp_across_list.  this adds one jump vector to the
74 // list of rules as long as the destination is interesting.
75 list process_warp_item(vector next_jump)
76 {
77 //log_it("mem: " + (string)llGetFreeMemory());
78     // calculate the number of jumps needed.
79     integer jumps = (integer)(llVecDist(next_jump, last_posn) / 10.0) + 1;
80     last_posn = next_jump;  // record for next check.
81     // set up our list which we'll replicate.
82     list rules = [ PRIM_POSITION, next_jump ];
83     // Try and avoid stack/heap collisions.
84     if (jumps > MAXIMUM_JUMPS_PER_CALL - 1) jumps = MAXIMUM_JUMPS_PER_CALL;
85     // add the rules repeatedly to get the effective overall jump done.
86     integer count = 1;
87     while ( (count = count << 1) < jumps)
88         rules += rules;
89     // magnify the rule list before really adding it.  this gets us to the proper
90     // final number of jumps.
91     return rules + llList2List(rules, (count - jumps) << 1 - 2, count);
92 }
93
94 // originally based on warpPos from lsl wiki but drastically modified.
95 // uses a set of transfer points instead of a single target.
96 list warp_across_list(list full_journey, integer forwards)
97 {
98     // make sure the list didn't run out.
99     if (llGetListLength(full_journey) == 0) return [];
100     if (forwards) {
101         // forwards traversal of the list.
102         vector next_jump = (vector)llList2String(full_journey, 0);
103         // shortcut the jumps if we're already there.
104         if (next_jump == llGetPos())
105             return warp_across_list(chop_list(full_journey, 1,
106                 llGetListLength(full_journey) - 1), forwards);
107         // calculate our trajectory for the next jump and add in all subsequent jumps.
108         return process_warp_item(next_jump)
109             + warp_across_list(chop_list(full_journey, 1, llGetListLength(full_journey) - 1), forwards);
110     } else {
111         // reverse traversal of the list.
112         vector next_jump = (vector)llList2String(full_journey, llGetListLength(full_journey) - 1);
113         // shortcut the jumps if we're already there.
114         if (next_jump == llGetPos())
115             return warp_across_list(chop_list(full_journey, 0,
116                 llGetListLength(full_journey) - 2), forwards);
117         // calculate our trajectory for the next jump and add in all subsequent jumps.
118         return process_warp_item(next_jump)
119             + warp_across_list(chop_list(full_journey, 0, llGetListLength(full_journey) - 2), forwards);
120     }
121 }
122
123 // the main function that performs the jaunting process.
124 // now also supports adding a rotation into the mix to avoid paying the extra delay
125 // cost of calling llSetRot.
126 jaunt(list full_journey, integer forwards, string command_used,
127     integer use_rotation, rotation rot, integer reply_to_request)
128 {
129 //log_it("jaunt: fullj=" + (string)full_journey + " forew=" + (string)forwards);
130     // set up our global variables...
131     last_posn = llGetPos();
132     // calculate the trip and run it.
133     list rot_add;
134     if (use_rotation)
135         rot_add = [ PRIM_ROTATION, rot ];
136     llSetPrimitiveParams(warp_across_list(full_journey, forwards) + rot_add);
137     if (reply_to_request) {
138         // pick out the last target in the list based on the direction we're moving.
139         integer last_indy = 0;
140         if (forwards == TRUE) last_indy = llGetListLength(full_journey) - 1;
141         vector last_jump = (vector)llList2String(full_journey, last_indy);
142         // final judge of success here is how close we got to the target.
143         integer landed_there = (llVecDist(llGetPos(), last_jump) <= PROXIMITY_REQUIRED);
144         send_reply(LINK_THIS, [landed_there, llGetPos()], command_used);
145     }
146 }
147
148 //////////////
149
150 // sets the object's speed to "new_velocity".
151 // if "local_axis" is TRUE, then it will be relative to the object's
152 // own local coordinates.
153 set_velocity(vector new_velocity, integer local_axis)
154 {
155     vector current_velocity = llGetVel();
156          
157     if (local_axis) {
158         rotation rot = llGetRot();
159         current_velocity /= rot;  // undo the rotation.
160     }
161     
162     new_velocity -= current_velocity;
163     new_velocity *= llGetMass();
164     
165     llApplyImpulse(new_velocity, local_axis);
166 }
167
168 // attempts to bring the object to a complete stop.
169 full_stop()
170 {
171     llSetForce(<0,0,0>, FALSE);
172     set_velocity(<0,0,0>, FALSE);
173 }
174
175 // sends an object back along its trajectory.
176 reverse_velocity(integer local_axis)
177 {
178     vector current_velocity = llGetVel();
179     if (local_axis) {
180         rotation rot = llGetRot();
181         current_velocity /= rot;  // undo the rotation.
182     }
183     vector new_velocity = -2 * current_velocity;
184     new_velocity *= llGetMass();
185     llApplyImpulse(new_velocity, local_axis);
186 }
187
188 // teleports to the new "location", if possible.  does not change object's phantom / physical
189 // states.  this will do the jaunt in multiple steps if the distance from here to "location"
190 // is too large.
191 apportioned_jaunt(vector location, string command_used, integer use_rotation, rotation rot,
192     integer should_send_reply)
193 {
194     if (!safe_for_entry(location)) {
195         // that's not good.  we should not allow the script to get broken.
196         if (should_send_reply)
197             send_reply(LINK_THIS, [FALSE, llGetPos()], command_used);
198         return;
199     }
200     // go to position specified, by leapfrogs if too long.
201     integer chunk_of_jump;
202     integer MAX_CHUNKS = 200;  // multiplies the distance a single jaunt can cover.
203     for (chunk_of_jump = 0; chunk_of_jump < MAX_CHUNKS; chunk_of_jump++) {
204         vector interim_vec = location - llGetPos();
205         float jump_dist = llVecDist(llGetPos(), location);
206         integer reply_needed = TRUE;
207         if (jump_dist > distance_safe_to_jaunt()) {
208             // the entire distance cannot be jumped.  do the part of it that fits.
209             float proportion_can_do = distance_safe_to_jaunt() / jump_dist;
210             interim_vec *= proportion_can_do;
211             reply_needed = FALSE;  // don't reply when this is not full jump.
212         }
213         interim_vec += llGetPos();  // bias jump back to where we are.
214 //log_it("jumping from " + (string)llGetPos() + " to " + (string)interim_vec);
215         jaunt([llGetPos(), interim_vec], TRUE, command_used, use_rotation, rot,
216             reply_needed && should_send_reply);
217         float dist_now = llVecDist(llGetPos(), interim_vec);
218         if (dist_now > PROXIMITY_REQUIRED) {
219 //log_it("failed to make interim jump, dist left is " + (string)dist_now);
220             // bail out.  we failed to get as far as we thought we should.
221             chunk_of_jump = MAX_CHUNKS + 10;
222             if (!reply_needed) {
223                 // we need to send the reply we hadn't sent yet.
224                 if (should_send_reply)
225                     send_reply(LINK_THIS, [FALSE, llGetPos()], command_used);
226             }
227         } else if (llVecDist(llGetPos(), location) <= PROXIMITY_REQUIRED) {
228             // leave loop for a different reason; we got there.
229             chunk_of_jump = MAX_CHUNKS + 10;
230             if (!reply_needed) {
231                 // we need to send the reply we hadn't sent yet.
232                 if (should_send_reply)
233                     send_reply(LINK_THIS, [TRUE, llGetPos()], command_used);
234             }
235         }
236     }
237 }
238
239 // the entire distance embodied in the list of targets.  this is a little smart,
240 // in that if there's only one target, we assume we're going from "here" to that
241 // one target.
242 float total_jaunt_distance(list targets)
243 {
244     if (!llGetListLength(targets)) return 0.0;
245     // add in "this" location if they omitted it.
246     if (llGetListLength(targets) < 2)
247         targets = [ llGetPos() ] + targets;
248     integer disindy;
249     float total_dist = 0.0;
250     vector prev = (vector)llList2String(targets, 0);
251     for (disindy = 1; disindy < llGetListLength(targets); disindy++) {
252         vector next = (vector)llList2String(targets, disindy);
253         total_dist += llVecDist(prev, next);
254         prev = next;
255     }
256     return total_dist;
257 }
258
259 // jaunts to a target via a set of intermediate locations.  can either go forwards
260 // through the list or backwards.
261 phantom_jaunt_to_list(list targets, integer forwards, string command_used,
262     integer use_rotation, rotation rot)
263 {
264     vector final_loc;
265     if (forwards) final_loc = (vector)llList2String(targets, llGetListLength(targets) - 1);
266     else final_loc = (vector)llList2String(targets, 0);
267
268 //hmmm: check each destination in list??
269     if (!safe_for_entry(final_loc)) {
270         // that's not good.  we should not allow the script to get broken.
271         send_reply(LINK_THIS, [FALSE, llGetPos()], command_used);
272         return;
273     }
274     
275     // this turns off the physics property for the object, so that jaunt and
276     // llSetPos will still work.  this became necessary due to havok4.
277     integer original_phantomosity = llGetStatus(STATUS_PHANTOM);
278     integer original_physicality = llGetStatus(STATUS_PHYSICS);
279     if (original_physicality != FALSE) llSetStatus(STATUS_PHYSICS, FALSE);
280     if (original_phantomosity != TRUE) llSetStatus(STATUS_PHANTOM, TRUE);
281
282     integer send_reply_still = TRUE;  // true if we should send a reply when done.
283
284     // iterate through our list of targets and either we will jaunt to the next
285     // place directly or we will have to wrap a few destinations due to sim crossing.
286     while (llGetListLength(targets) > 0) {
287         vector next_loc;
288         if (forwards) next_loc = (vector)llList2String(targets, 0);
289         else next_loc = (vector)llList2String(targets, llGetListLength(targets) - 1);
290         if (outside_of_sim(next_loc)) {
291             log_it("bad jaunt path: first target is out of sim.");
292             send_reply(LINK_THIS, [FALSE, llGetPos()], command_used);
293             return;  // skip that badness.
294         }
295         
296         // check how much total distance we have in the path that's left.  if it's under our
297         // limit, then we'll just take it in one jump.
298         float total_dist = total_jaunt_distance(targets);
299
300         // if we're below the threshold, we'll just jump now.
301         integer already_jumped = FALSE;
302         if (total_dist < distance_safe_to_jaunt()) {
303             jaunt(targets, forwards, command_used, use_rotation, rot, TRUE);
304             targets = [];  // reset the list now.
305             send_reply_still = FALSE;  // we have already sent the reply in jaunt().
306             already_jumped = TRUE;  // don't do anything else.
307         }
308         if (!already_jumped) {
309             vector next_plus_1 = ZERO_VECTOR;  // default cannot fail our "is inside sim" check.
310             if (llGetListLength(targets) > 1) {
311                 if (forwards) next_plus_1 = (vector)llList2String(targets, 1);
312                 else next_plus_1 = (vector)llList2String(targets, llGetListLength(targets) - 2);
313             }
314             if (outside_of_sim(next_plus_1)) {
315 //hmmm: eventually find all the negative ones in a row and do them in combo, rather than
316 //      just giving up here and doing a jaunt to the rest..
317                 jaunt(targets, forwards, command_used, use_rotation, rot, TRUE);
318                 targets = [];  // reset the list now.
319                 send_reply_still = FALSE;  // we have already sent the reply in jaunt().
320             } else {
321                 // we've passed the negativity test, so we can at least jump to the next place.
322                 
323                 // zap the next location, since we're about to jump there.
324                 integer zap_pos = 0;
325                 if (!forwards) zap_pos = llGetListLength(targets) - 1;
326                 targets = llDeleteSubList(targets, zap_pos, zap_pos);        
327         
328                 // only bother jumping if we're not already there.
329                 if (llVecDist(next_loc, llGetPos()) > PROXIMITY_REQUIRED) {
330                     apportioned_jaunt(next_loc, command_used, use_rotation, rot, FALSE);
331                 }
332             }
333         }
334     }
335
336     if (send_reply_still) {
337         integer yippee = TRUE;  // assume we succeeded until we prove otherwise.
338         if (llVecDist(final_loc, llGetPos()) > PROXIMITY_REQUIRED)
339             yippee = FALSE;
340         send_reply(LINK_THIS, [yippee, llGetPos()], command_used);
341     }
342
343     // return to prior characteristics.
344     if (original_phantomosity != TRUE) llSetStatus(STATUS_PHANTOM, original_phantomosity);
345     if (original_physicality != FALSE) llSetStatus(STATUS_PHYSICS, original_physicality);
346 }
347
348 // implements our API for jumping around.
349 handle_link_message(integer sender, integer huff_id, string msg, key id)
350 {
351     // separate the list out
352     list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
353     if (msg == JAUNT_COMMAND) {
354         // use list to string to avoid non-conversions.
355         vector v = (vector)llList2String(parms, 0);
356         rotation rot = <0.0, 0.0, 0.0, 1.0>;
357         integer gave_rot = llGetListLength(parms) > 1;
358         if (gave_rot) rot = (rotation)llList2String(parms, 1);  // ooh, they gave us a rotational value also.
359 //        log_it("gave rot? " + (string)gave_rot + " rot=" + (string)rot);
360         phantom_jaunt_to_list([llGetPos(), v], TRUE, msg, gave_rot, rot);
361     } else if (msg == FULL_STOP_COMMAND) {
362         full_stop();
363     } else if (msg == REVERSE_VELOCITY_COMMAND) {
364         reverse_velocity(FALSE);
365     } else if (msg == SET_VELOCITY_COMMAND) {            
366         vector v = (vector)llList2String(parms, 0);
367 //log_it("jaunting lib received set velocity request for " + (string)v);
368         set_velocity(v, FALSE);
369     } else if (msg == JAUNT_UP_COMMAND) {
370         phantom_jaunt_to_list([ llGetPos(), llGetPos() + <0.0, 0.0, llList2Float(parms, 0)> ],
371             TRUE, msg, FALSE, ZERO_ROTATION);
372     } else if (msg == JAUNT_DOWN_COMMAND) {
373         phantom_jaunt_to_list([ llGetPos(), llGetPos() + <0.0, 0.0, -llList2Float(parms, 0)> ],
374             TRUE, msg, FALSE, ZERO_ROTATION);
375     } else if (msg == JAUNT_LIST_COMMAND) {
376         string destination_list = llList2String(parms, 0);
377         list targets = llParseString2List(destination_list, [HUFFWARE_ITEM_SEPARATOR], []);
378         destination_list = "";
379         integer forwards = TRUE;
380         // snag the directionality for the list, if specified.
381         if (llGetListLength(parms) > 1) forwards = llList2Integer(parms, 1);
382         rotation rot = <0.0, 0.0, 0.0, 1.0>;
383         integer gave_rot = llGetListLength(parms) > 2;
384         if (gave_rot) rot = llList2Rot(parms, 2);  // ooh, they gave us a rotational value also.        
385         phantom_jaunt_to_list(targets, forwards, msg, gave_rot, rot);
386         targets = [];
387     }
388 }
389
390 //////////////
391 // from hufflets...
392
393 integer debug_num = 0;
394
395 // a debugging output method.  can be disabled entirely in one place.
396 log_it(string to_say)
397 {
398     debug_num++;
399     // tell this to the owner.    
400     llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
401     // say this on open chat, but use an unusual channel.
402 //    llSay(108, llGetScriptName() + "--" + (string)debug_num + ": " + to_say);
403 }
404 //////////////
405
406 // a simple version of a reply for a command that has been executed.  the parameters
407 // might contain an outcome or result of the operation that was requested.
408 send_reply(integer destination, list parms, string command)
409 {
410     llMessageLinked(destination, JAUNT_HUFFWARE_ID + REPLY_DISTANCE, command,
411         llDumpList2String(parms, HUFFWARE_PARM_SEPARATOR));
412 }
413
414 // returns TRUE if the value in "to_check" specifies a legal x or y value in a sim.
415 integer valid_sim_value(float to_check)
416 {
417     if (to_check < 0.0) return FALSE;
418     if (to_check >= 257.0) return FALSE;
419     return TRUE;
420 }
421
422 integer outside_of_sim(vector to_check)
423 {
424     return !valid_sim_value(to_check.x) || !valid_sim_value(to_check.y);
425 }
426
427 ///////////////
428
429 // returns the portion of the list between start and end, but only if they are
430 // valid compared with the list length.  an attempt to use negative start or
431 // end values also returns a blank list.
432 list chop_list(list to_chop, integer start, integer end)
433 {
434     integer last_len = llGetListLength(to_chop) - 1;
435     if ( (start < 0) || (end < 0) || (start > last_len) || (end > last_len) ) return [];
436     return llList2List(to_chop, start, end);
437 }
438
439 //////////////
440
441 //////////////
442 // huffware script: auto-retire, by fred huffhines, version 2.5.
443 // distributed under BSD-like license.
444 //   !!  keep in mind that this code must be *copied* into another
445 //   !!  script that you wish to add auto-retirement capability to.
446 // when a script has auto_retire in it, it can be dropped into an
447 // object and the most recent version of the script will destroy
448 // all older versions.
449 //
450 // the version numbers are embedded into the script names themselves.
451 // the notation for versions uses a letter 'v', followed by two numbers
452 // in the form "major.minor".
453 // major and minor versions are implicitly considered as a floating point
454 // number that increases with each newer version of the script.  thus,
455 // "hazmap v0.1" might be the first script in the "hazmap" script continuum,
456 // and "hazmap v3.2" is a more recent version.
457 //
458 // example usage of the auto-retirement script:
459 //     default {
460 //         state_entry() {
461 //            auto_retire();  // make sure newest addition is only version of script.
462 //        }
463 //     }
464 // this script is partly based on the self-upgrading scripts from markov brodsky
465 // and jippen faddoul.
466 //////////////
467 auto_retire() {
468     string self = llGetScriptName();  // the name of this script.
469     list split = compute_basename_and_version(self);
470     if (llGetListLength(split) != 2) return;  // nothing to do for this script.
471     string basename = llList2String(split, 0);  // script name with no version attached.
472     string version_string = llList2String(split, 1);  // the version found.
473     integer posn;
474     // find any scripts that match the basename.  they are variants of this script.
475     for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
476         string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
477         if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
478             // found a basic match at least.
479             list inv_split = compute_basename_and_version(curr_script);
480             if (llGetListLength(inv_split) == 2) {
481                 // see if this script is more ancient.
482                 string inv_version_string = llList2String(inv_split, 1);  // the version found.
483                 // must make sure that the retiring script is completely the identical basename;
484                 // just matching in the front doesn't make it a relative.
485                 if ( (llList2String(inv_split, 0) == basename)
486                     && ((float)inv_version_string < (float)version_string) ) {
487                     // remove script with same name from inventory that has inferior version.
488                     llRemoveInventory(curr_script);
489                 }
490             }
491         }
492     }
493 }
494 //
495 // separates the base script name and version number.  used by auto_retire.
496 list compute_basename_and_version(string to_chop_up)
497 {
498     // minimum script name is 2 characters plus a version.
499     integer space_v_posn;
500     // find the last useful space and 'v' combo.
501     for (space_v_posn = llStringLength(to_chop_up) - 3;
502         (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
503         space_v_posn--) {
504         // look for space and v but do nothing else.
505     }
506     if (space_v_posn < 2) return [];  // no space found.
507     // now we zoom through the stuff after our beloved v character and find any evil
508     // space characters, which are most likely from SL having found a duplicate item
509     // name and not so helpfully renamed it for us.
510     integer indy;
511     for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
512         if (llGetSubString(to_chop_up, indy, indy) == " ") {
513             // found one; zap it.  since we're going backwards we don't need to
514             // adjust the loop at all.
515             to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
516         }
517     }
518     string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
519     // ditch the space character for our numerical check.
520     string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
521     // strip out a 'v' if there is one.
522     if (llGetSubString(chop_suffix, 0, 0) == "v")
523         chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
524     // if valid floating point number and greater than zero, that works for our version.
525     string basename = to_chop_up;  // script name with no version attached.
526     if ((float)chop_suffix > 0.0) {
527         // this is a big success right here.
528         basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
529         return [ basename, chop_suffix ];
530     }
531     // seems like we found nothing useful.
532     return [];
533 }
534 //
535 //////////////
536
537 default {
538     state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
539     on_rez(integer parm) { state rerun; }
540 }
541 state rerun { state_entry() { state default; } }
542
543 state real_default
544 {
545     state_entry() { auto_retire(); }
546     
547     link_message(integer sender, integer huff_id, string msg, key id) {
548         if (huff_id != JAUNT_HUFFWARE_ID) return;  // not our responsibility.
549         handle_link_message(sender, huff_id, msg, id);
550     }
551 }
552