0e162579c415d754eb57a535a914fc00d7725098
[feisty_meow.git] / huffware / huffotronic_eepaw_knowledge_v60.9 / rezzeroni_v20.2.lsl
1 
2 // huffware script: rezzeroni, by fred huffhines
3 //
4 // a script that rezzes objects for you according to some game plan, which is defined
5 // in a notecard.  this script can also run periodically to create temporary objects
6 // on a schedule.  the real power of this script comes from its ability to rez up a
7 // collection of objects using relative positioning.  the newly rezzed objects can be
8 // given a startup notecard also, which is held inside the rezzer.
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 // * note regarding the periodic rezzing capability: the objects that are created
15 //   really need to be marked as temporary objects.  otherwise the newly rezzed
16 //   objects will clutter up the sim, flooding it with objects, and will eventually
17 //   go over the owner's prim limit.  that's not cool.  so if you use a periodic
18 //   rezzer, make sure your contained objects are temporaries.
19
20 // global constants...
21
22 integer DEBUGGING = FALSE;  // produces noisy diagnostics if enabled.
23
24 integer SUPER_NOISY_DEBUG = FALSE;  // extraordinarily noisy items are logged.
25
26 float HOME_PROXIMITY = 0.1;  // object must be this close to home before we quit.
27
28 integer PERFORM_JAUNTS = TRUE;  // set to true if rezzer should teleport to rez locations.
29
30 integer OBJECT_STARTUP_PARM = 14042;
31 //hmmm: may want this to come from notecard someday.
32
33 // notecard configurable variables:
34 integer owner_only;  // is the owner the only one allowed to run this rezzer?
35 vector obj_position;  // absolute position for object to rez at.
36 integer obj_is_singleton;  // should the object not be rezzed if similar named one exists?
37 //hmmm: support configurable singleton sensor range?
38 integer rezzed_at_once;  // the number of each object that are rezzed at the same time.
39 float obj_rezzing_period;  // how often the objects are rezzed up, in seconds.
40 integer max_run_time;  // how long to run once enabled, in seconds.  1200=20 minutes.
41 string gift_card_name;  // a gift object placed inside newly rezzed object.
42 vector obj_max_offset_add = <2.0, 2.0, 2.0>;  // random offsets can be this large.
43 vector obj_max_rotation_add = <TWO_PI, TWO_PI, TWO_PI>;  // max random rotation.
44 // these object variables can be randomized.  "random" means pick a random item.
45 string obj_name;  // what is the object called?  "ALL" means all items.
46 string obj_offset;  // how far away from current position object rezzes.
47 string obj_rotation;  // euler rotation for object to point at upon rez.
48 string obj_multi_offset;  // the amount to add to the offset each time for count > 1.
49 string obj_multi_angle;  // the angle added to each rez.
50 //hmmm: also allow position to be random?
51 integer DIE_ON_DEMAND_CHANNEL = 4826;  // default, can be loaded from notecard.
52
53 // assorted global variables...
54 integer is_enabled_now;  // records whether the rezzer is running a plan right now.
55 integer when_last_enabled;  // tracks time when device was turned on.
56 // notecard globals.
57 string global_notecard_name;  // name of our notecard in the object's inventory.
58 integer response_code;  // set to uniquely identify the notecard read in progress.
59 list global_config_list;  // a collection of configuration parameters from our notecard.
60 // globals tracking the current rezzing run.
61 list already_done_items;  // the items that are finished being rezzed, since they have no rate.
62 integer rezzed_overall;  // a count of the number of rezzes during this run.
63 list gift_stack;  // a stack of items to hand to newly rezzed objects.
64 // somewhat ugly rez plan globals.
65 integer global_config_index;  // where are we in the config list?
66 string global_singleton_checker;  // is a singleton object being sensor checked?
67 integer global_found_the_singleton;  // did we see the singleton object requested?
68 integer sensor_checks_pending;  // how many sensor scans are awaited (zero or one).
69 integer recent_items_rezzed;  // how many got rezzed in last run?
70 // object creation loop vars:
71 integer current_object_index = 0;  // which object are we at, in the current section?
72 integer current_count;  // the number already created of the current object.
73
74 vector main_home;
75     // the home location for the rezzer object.  it will return to this after
76     // performing the requested rezzes.
77 rotation main_rotate;
78     // original rotation of the rezzer object.
79
80 integer jaunt_responses_awaited;
81     // the number of jumps still pending.  should never be more than one in this script.
82
83 // constants that should generally not be modified unless you are an expert...
84
85 string REZZERONI_SIGNATURE = "#rezzeroni";  // the expected first line of our notecards.
86
87 float MAXIMUM_REZ_DISTANCE = 9.9;
88   // the farthest distance that we can rez an object at reliably.  if the distance is
89   // larger than this, then the rezzer will teleport to the work zone.
90
91 float BASE_TIMER_PERIOD = 0.04;  // the rate at which the timer runs to rez objects.
92
93 // requires noteworthy library v9.3 or better.
94 //////////////
95 // do not redefine these constants.
96 integer NOTEWORTHY_HUFFWARE_ID = 10010;
97     // the unique id within the huffware system for the noteworthy script to
98     // accept commands on.  this is used in llMessageLinked as the num parameter.
99 string HUFFWARE_PARM_SEPARATOR = "{~~~}";
100     // this pattern is an uncommon thing to see in text, so we use it to separate
101     // our commands in link messages.
102 string HUFFWARE_ITEM_SEPARATOR = "{|||}";
103     // used to separate lists of items from each other when stored inside a parameter.
104     // this allows lists to be passed as single string parameters if needed.
105 integer REPLY_DISTANCE = 100008;  // offset added to service's huffware id in reply IDs.
106 // commands available via the noteworthy library:
107 string NOTECARD_READ_CONTINUATION = "continue!";
108     // returned as first parameter if there is still more data to handle.
109 // commands available via the noteworthy library:
110 string READ_NOTECARD_COMMAND = "#read_note#";
111     // command used to tell the script to read notecards.  needs a signature to find
112     // in the card as the first parameter, and a randomly generated response code for
113     // the second parameter.  the response code is used to uniquely identify a set of
114     // pending notecard readings (hopefully).  the signature can be blank.
115     // the results will be fired back as the string value returned, which will have
116     // as first element the notecard's name (or BAD_NOTECARD_INDICATOR if none was
117     // found) and as subsequent elements an embedded list that was read from the
118     // notecard.  this necessarily limits the size of the notecards that we can read
119     // and return.
120 string READ_SPECIFIC_NOTECARD_COMMAND = "#read_thisun#";
121     // like the read notecard command, but specifies the notecard name to use.  only that
122     // specific notecard file will be consulted.  first and second parm are still signature
123     // and response code, third parm is the notecard name.
124 //
125 //////////////
126 // joins a list of parameters using the parameter sentinel for the library.
127 string wrap_parameters(list to_flatten)
128 { return llDumpList2String(to_flatten, HUFFWARE_PARM_SEPARATOR); }
129 //////////////
130
131 // looks for a notecard with our signature in it and reads the configuration.
132 // an empty string is returned if we couldn't find anything.
133 request_configuration()
134 {
135     global_notecard_name = "";
136     response_code = 0;
137     
138     // try to find a notecard with our configuration.
139     response_code = -1 * (integer)randomize_within_range(23, 80000, FALSE);
140     string parms_sent = wrap_parameters([REZZERONI_SIGNATURE, response_code]);
141     llMessageLinked(LINK_THIS, NOTEWORTHY_HUFFWARE_ID, READ_NOTECARD_COMMAND,
142          parms_sent);
143 }
144
145 // processes link messages received from support libraries.
146 integer handle_link_message(integer which, integer num, string msg, key id)
147 {
148     list parms;
149     if (num == JAUNT_HUFFWARE_ID + REPLY_DISTANCE) {
150         // did we get a response to a teleport request?
151         if (msg == JAUNT_LIST_COMMAND) {
152 //log_it("msg: " + (string)num + " str=" + str);
153             jaunt_responses_awaited--;  // one less response being awaited.
154             if (jaunt_responses_awaited < 0) {
155                 log_it("error, jaunts awaited < 0!");
156                 jaunt_responses_awaited = 0;
157             }
158             // unpack the reply.
159             parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
160             integer last_jaunt_was_success = llList2Integer(parms, 0);
161             vector posn = (vector)llList2String(parms, 1);
162     //log_it("got a reply for a jaunt request, success=" + (string)last_jaunt_was_success + " posn=" + (string)posn);
163     //do anything with the parms?
164         }
165         return FALSE;
166     }
167     if ( (num != NOTEWORTHY_HUFFWARE_ID + REPLY_DISTANCE)
168         || (msg != READ_NOTECARD_COMMAND) ) return FALSE;  // not for us.
169
170     // process the result of reading the notecard.
171     parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
172     string notecard_name = llList2String(parms, 0);
173     integer response_for = llList2Integer(parms, 1);
174     if (response_for != response_code) return FALSE;  // oops, this isn't for us.
175     // make sure if we are being told to keep going.
176     if (notecard_name == NOTECARD_READ_CONTINUATION) {
177 //log_it("continuation of notecard seen.");
178         // snag all but the first two elements for our config now.
179         global_config_list += llList2List(parms, 2, -1);
180         // we're not done reading yet, so fall out to the false return.
181     } else if (notecard_name != "bad_notecard") {
182         // a valid notecard has been found and we're done with it now.
183         global_notecard_name = notecard_name;
184         // snag all but the first two elements for our config now.
185         global_config_list += llList2List(parms, 2, -1);
186         global_config_index = 0;
187         // signal that we're done getting the config.
188         return TRUE;
189     } else {
190         // we hated the notecards we found, or there were none.
191         log_it("sorry, no notecards starting with '"
192             + REZZERONI_SIGNATURE
193             + "'; cannot rez yet.");
194     }
195     return FALSE;
196 }
197
198 ///////////////
199
200 // requires jaunting library v10.5 or greater.
201 //////////////
202 // do not redefine these constants.
203 integer JAUNT_HUFFWARE_ID = 10008;
204     // the unique id within the huffware system for the jaunt script to
205     // accept commands on.  this is used in llMessageLinked as the num parameter.
206 // commands available via the jaunting library:
207 string JAUNT_COMMAND = "#jaunt#";
208     // command used to tell jaunt script to move object.  pass a vector with the location.
209 string JAUNT_LIST_COMMAND = "#jauntlist#";
210     // like regular jaunt, but expects a list of vectors as the first parameter; this list
211     // should be in the jaunter notecard format (separated by pipe characters).
212     // the second parameter, if any, should be 1 for forwards traversal and 0 for backwards.
213 //
214 //////////////
215 // encases a list of vectors in the expected character for the jaunting library.
216 string wrap_vector_list(list to_wrap)
217 { return llDumpList2String(to_wrap, HUFFWARE_ITEM_SEPARATOR); }
218 //////////////
219
220 // asks the jaunting library to take us to the target using a list of waypoints.
221 request_jaunt(list full_journey, integer forwards)
222 {
223     if (PERFORM_JAUNTS) {
224         jaunt_responses_awaited++;
225         llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, JAUNT_LIST_COMMAND,
226             wrap_vector_list(full_journey)
227             + HUFFWARE_PARM_SEPARATOR + (string)forwards);
228     }
229 }
230
231 // jaunts back to our home location.
232 attempt_to_go_home()
233 {
234     // jump back to home.
235     request_jaunt([llGetPos(), main_home], TRUE);
236 }
237
238 ///////////////
239
240 // creates the real list of items to rez based on the obj_name variable.
241 // this might turn out to be an empty list.
242 list build_rezzing_list()
243 {
244     list objects_to_rez;  // what items are we expected to create in this round?
245     
246     integer num_inv = llGetInventoryNumber(INVENTORY_OBJECT);
247     if (num_inv == 0) {
248         log_it("Inventory is empty; please add objects to rez.");
249         llSetTimerEvent(0.0);  // stop the timer; nothing further to do.
250         return [];
251     }
252 //hmmm: all has never been tested.
253     if (obj_name == "ALL") {
254         integer inv;
255         for (inv = 0; inv < num_inv; inv++) {
256             objects_to_rez += [ llGetInventoryName(INVENTORY_OBJECT, inv) ];
257         }
258         obj_is_singleton = FALSE;  // flag makes no sense for multiple objects.
259     } else {
260         // we know we are not being told to do a multiple set of objects.
261         if (obj_name == "random") {
262             // special keyword for a random object.
263             integer inv = (integer)randomize_within_range(0.0, num_inv, FALSE);
264             obj_name = llGetInventoryName(INVENTORY_OBJECT, inv);
265             if (SUPER_NOISY_DEBUG)
266                 log_it("chose item #" + (string)inv + " randomly (called '" + obj_name + "').");
267         } else {
268             // a normal singular object to create.
269             if (find_in_inventory(obj_name, INVENTORY_OBJECT) < 0) {
270                 // this item is bogus!
271                 log_it("item '" + obj_name + "' is bogus!  not in inventory.");
272                 obj_name = "";  // reset it.
273             }
274         }
275         // now make sure we didn't already do this one.
276         if (find_in_list(already_done_items, obj_name) >= 0) {
277 //            if (SUPER_NOISY_DEBUG)
278 //                log_it("item '" + obj_name + "' has already been rezzed.  skipping it.");
279             obj_name = "";
280         } else {
281             if (obj_name != "") objects_to_rez += [ obj_name ];
282         }
283     }
284     
285     return objects_to_rez;
286 }
287
288 // checks whether the object is even supposed to be a singleton or not.
289 // if it should be a singleton, then we run a sensor ping to see if it exists in the
290 // vicinity or not.  if it's not there, we'll eventually hear about it and create it.
291 integer validate_singleton_object()
292 {
293     // see if there's only supposed to be one of these objects.
294     if (!obj_is_singleton) return TRUE;  // just keep going; it's not one.
295     if (obj_name == "") return TRUE;  // this should never happen.  pretend it's okay.
296
297     // okay, we know we should have a singleton.  now did we already check for its presence?    
298     if (global_singleton_checker == "") {
299         // have not scanned yet; postpone the rezzing action and start a sensor for the object.
300         sensor_checks_pending++;
301         global_singleton_checker = obj_name;
302         // maximally distant ping in full arc on all types of objects we can see.
303         llSensor(obj_name, NULL_KEY, AGENT|ACTIVE|PASSIVE, 96, PI);
304         if (DEBUGGING) log_it("scanning for singleton item '" + obj_name + "' now.");
305         return FALSE;
306     } else {
307         // we should have finished a sensor probe now, since there were no checks pending
308         // and since this is a singleton.
309         if (global_found_the_singleton) {
310             if (DEBUGGING)
311                 log_it("singleton item '" + obj_name + "' already present; not re-rezzing.");
312             already_done_items += [ obj_name ];
313 //if (TERMINATE_IF_PRESENT)
314 string DIE_ON_DEMAND_MESSAGE = "die-on-demand";
315 llShout(DIE_ON_DEMAND_CHANNEL, DIE_ON_DEMAND_MESSAGE);
316         } else {
317             if (DEBUGGING)
318                 log_it("singleton item '" + obj_name + "' is absent; will rez one now if possible.");
319         }
320         return TRUE;
321     }
322 }
323
324 // operates on the current parameters to rez an object or objects.
325 integer perform_rezzing_action()
326 {
327     vector current_home = llGetPos();
328     list objects_to_rez = build_rezzing_list();
329 //log_it("rezzing " + (string)llGetListLength(objects_to_rez) + " objects");
330     // create the specified number of the items we were told to use.
331     integer num_objects = llGetListLength(objects_to_rez);
332     if (num_objects == 0) {
333         // nothing to do at this point.
334 //        if (SUPER_NOISY_DEBUG) log_it("no items in rez list; bailing out.");
335         return TRUE;
336     }
337     // iterate across each object in inventory.
338     integer inv;
339     for (inv= current_object_index; inv < num_objects; inv++) {
340 //log_it("object index " + (string)inv);
341         // rez as many of the object as we were requested to.
342         string new_rezzee = llList2String(objects_to_rez, inv);
343         integer counter;
344         for (counter = current_count; counter < rezzed_at_once; counter++) {
345             // make sure we've never rezzed a non-periodic with this name before.
346             if (find_in_list(already_done_items, new_rezzee) < 0) {
347 ///log_it("haven't rezzed '" + new_rezzee + "' yet.");
348                 vector rez_place = obj_position;
349 //log_it("configd rezplace=" + (string)rez_place);
350                 if (rez_place == <0.0, 0.0, 0.0>) {
351                    // they did not give us an absolute position, so use the home position.
352                    rez_place = main_home;
353 //log_it("rehomed rezplace=" + (string)rez_place);
354                 }
355                 vector calc_offset = maybe_randomize_offset(obj_offset, obj_max_offset_add);
356                 if (calc_offset != <0.0, 0.0, 0.0>) {
357                     // the offset is non-zero, so apply it to the rez place.
358                     rez_place += calc_offset * main_rotate;
359 //log_it("offsetd rezplace=" + (string)rez_place);
360                 }
361                 // add in the multiplier offset if this is not the first rezzing of this item.
362                 rez_place += ((float)counter)
363                     * maybe_randomize_offset(obj_multi_offset, obj_max_offset_add)
364                     * main_rotate;
365 //log_it("home=" + (string)main_home + " rezplace=" + (string)rez_place);
366     
367                 // check that we are not too far away.
368                 float rez_distance = llVecDist(rez_place, llGetPos());
369                 if (rez_distance > MAXIMUM_REZ_DISTANCE) {
370                     // try to jump there if we're too far away to rez from here.
371                     if (DEBUGGING)
372                         log_it("distance is " + (string)rez_distance
373                             + "; jaunting closer.");
374                     request_jaunt([llGetPos(), rez_place], TRUE);
375                     current_count = counter;
376                     current_object_index = inv;
377                     return FALSE;
378                 }
379
380                 // make sure we aren't already done with this object, if it's a singleton.
381                 integer keep_going = validate_singleton_object();
382                 if (!keep_going) {
383                     if (SUPER_NOISY_DEBUG) log_it("must sense for singleton, bailing.");
384                     current_count = counter;
385                     current_object_index = inv;
386                     return FALSE;  // we can't proceed yet.  must check for singleton.
387                 }
388
389 //hmmm: we need to offer an angular offset also!
390 //  this would mean that the multiple guys could be set at nice different angles.
391
392                 // now check again whether to rez this or not; the validation method adds it to the
393                 // done list if the object is already present and is a singleton.
394                 if (find_in_list(already_done_items, new_rezzee) < 0) {
395                     if (DEBUGGING)
396                         log_it("now rezzing '" + new_rezzee + "', rot " + obj_rotation);
397                     vector rota = maybe_randomize_rotation(obj_rotation, obj_max_rotation_add);
398                     rotation real_rot = llEuler2Rot(rota * DEG_TO_RAD);
399
400 //hmmm: might be nice to offer an absolute version!
401 // that accounts for the specific position and offset, rather
402 // than relative to rezzer.  angle should be kept absolutist also.
403
404                     // add in our rotation component as the rezzer.
405                     real_rot *= main_rotate;
406 //this is bunkum.
407                     // add in the angular addition for multiple object creation.
408                     real_rot *= llEuler2Rot(
409                         maybe_randomize_rotation(obj_multi_angle, obj_max_rotation_add)
410                         * (float)counter * DEG_TO_RAD);
411                     recent_items_rezzed++;  // we got to do something.
412                     // rez it up since we know what is wanted and where, plus we're in range.
413                     llRezObject(new_rezzee, rez_place, ZERO_VECTOR, real_rot, OBJECT_STARTUP_PARM);
414                     rezzed_overall++;  // one more item.
415                     if (gift_card_name != "") {
416                         // this guy needs its startup gift.
417                         string inv_name = gift_card_name + (string)rezzed_overall;
418                         if (find_in_inventory(inv_name, INVENTORY_NOTECARD) >= 0) {
419                             // this inventory item exists, so use it.
420                             gift_stack += [ inv_name];
421                         }
422                     }
423                     global_singleton_checker = "";  // reset singleton checker for next object.
424                 } else {
425                     if (SUPER_NOISY_DEBUG) log_it("not rezzing, already saw it in list");
426                 }
427             }
428         }
429         current_count = 0;  // got to the full count for the object.
430         // if this is a non-periodic object, we don't need any more runs.
431         if (obj_rezzing_period == 0.0) {
432             if (find_in_list(already_done_items, new_rezzee) < 0) {
433                 if (SUPER_NOISY_DEBUG) log_it("adding newly done: " + (string)new_rezzee);
434                 already_done_items += [ new_rezzee ];
435             }
436         }
437     }
438     current_object_index = 0;  // reset the current object index.
439
440     return TRUE;
441 }
442
443 ///////////////
444
445 // returns true if the state should change.
446 integer handle_timer()
447 {
448     // make sure we run at the fast rate; we'll reset the rate elsewhere if needed.
449     llSetTimerEvent(BASE_TIMER_PERIOD);
450
451     if (!is_enabled_now) {
452         if (jaunt_responses_awaited > 0) {
453             if (SUPER_NOISY_DEBUG) log_it("done rez, but jaunts awaited.");
454             return FALSE;
455         }
456         if (llVecDist(llGetPos(), main_home) > HOME_PROXIMITY) {
457             if (SUPER_NOISY_DEBUG) log_it("done rez, but not at home.");
458             attempt_to_go_home();
459             return FALSE;
460         }
461         if (llGetListLength(gift_stack)) {
462             if (SUPER_NOISY_DEBUG) log_it("done rez, but still pending gifts.");
463             return FALSE;
464         }
465         if (SUPER_NOISY_DEBUG) log_it("done rez, totally done.");
466         llSetTimerEvent(0.0);
467         return TRUE;
468     }
469
470     // check whether the max run time has elapsed.    
471     integer run_time = llAbs(llGetUnixTime() - when_last_enabled);
472     if (run_time > max_run_time) {
473         log_it("Maximum run time elapsed; shutting down.");
474         is_enabled_now = FALSE;
475         llSetTimerEvent(0.0);
476         return TRUE;
477     }
478
479     // we have not gotten a configuration yet.
480     if (global_notecard_name == "") {
481 ///        if (SUPER_NOISY_DEBUG) log_it("in timer with no notecard???");
482         return FALSE;
483     }
484
485     // crank to next step in config.
486     if (!run_through_rez_plan()) {
487 //log_it("told done with rez plan");
488         // we were told not to keep going.  if this is a periodic run,
489         // then we'll snooze until the next time.
490         if (obj_rezzing_period != 0.0) {
491             llSetTimerEvent(obj_rezzing_period);
492             finished_file = FALSE;
493         }
494     }
495     // even if the run were done, we still have to handle termination conditions.
496     return FALSE;
497 }
498
499 // processes requests made by avatars via chat.
500 integer handle_hearing_voices(integer channel, string name, key id, string message)
501 {
502     string rez_command = "#rez";
503     // is this our command prefix?
504     if (is_prefix(message, rez_command)) {
505         // we found a command.  which specific one?
506         string parm = llDeleteSubString(message, 0, llStringLength(rez_command));
507             // eat the command portion, plus a space.
508         if ( (parm == "on") && !is_enabled_now) {
509             is_enabled_now = TRUE;
510             return TRUE;
511         } else if ( (parm == "off") && is_enabled_now) {
512             is_enabled_now = FALSE;
513             return TRUE;
514         }
515     }
516     return FALSE;
517 }
518
519 // a helper function that turns the word "random" into a random value.
520 // we will never allow a negative z value for the random amount, since that will often be
521 // wrong (rezzing underneath where the object lives, e.g.).  this implies always
522 // putting the rezzer below where one wants to see the random rezzing action.
523 vector maybe_randomize_offset(string vector_or_not, vector largest_add)
524 {
525     if (vector_or_not == "random") {
526         vector just_xy = <largest_add.x, largest_add.y, 0.0>;
527         vector just_z = <0.0, 0.0, largest_add.z>;
528         vector to_return = random_bound_vector(<0.0, 0.0, 0.0>, just_xy, TRUE)
529             + random_bound_vector(<0.0, 0.0, 0.0>, just_z, FALSE);
530 //log_it("calc rand off: " + (string)to_return);            
531         return to_return;
532     } else {
533         return (vector)vector_or_not;
534     }
535 }
536
537 vector maybe_randomize_rotation(string vector_or_not, vector largest_add)
538 {
539     if (vector_or_not == "random") {
540         vector to_return = random_bound_vector(<0.0, 0.0, 0.0>, largest_add, TRUE);
541 //log_it("calc rand rot: " + (string)to_return);            
542         return to_return;
543     } else {
544         return (vector)vector_or_not;
545     }
546 }
547
548 // returns a random vector where x,y,z will be between "minimums" and "maximums"
549 // x,y,z components.  if "allow_negative" is true, then any component will
550 // randomly be negative or positive.
551 vector random_bound_vector(vector minimums, vector maximums, integer allow_negative)
552 {
553     return <randomize_within_range(minimums.x, maximums.x, allow_negative),
554         randomize_within_range(minimums.y, maximums.y, allow_negative),
555         randomize_within_range(minimums.z, maximums.z, allow_negative)>;
556 }
557
558 // consumes the notecard in a very application specific way
559 // to retrieve the configurations for the items to rez.
560 parse_variable_definition(string to_parse)
561 {
562     string content;  // filled after finding a variable name.
563
564     if ( (content = get_variable_value(to_parse, "name")) != "")
565         obj_name = content;
566     else if ( (content = get_variable_value(to_parse, "position")) != "")
567         obj_position = (vector)content;
568     else if ( (content = get_variable_value(to_parse, "offset")) != "")
569         obj_offset = content;
570     else if ( (content = get_variable_value(to_parse, "rotation")) != "")
571         obj_rotation = content;
572     else if ( (content = get_variable_value(to_parse, "multi_offset")) != "")
573         obj_multi_offset = content;
574     else if ( (content = get_variable_value(to_parse, "multi_angle")) != "")
575         obj_multi_angle = content;
576     else if ( (content = get_variable_value(to_parse, "singleton")) != "")
577         obj_is_singleton = (integer)content;
578     else if ( (content = get_variable_value(to_parse, "count")) != "")
579         rezzed_at_once = (integer)content;
580     else if ( (content = get_variable_value(to_parse, "rate")) != "")
581         obj_rezzing_period = (float)content;
582     else if ( (content = get_variable_value(to_parse, "max_run")) != "")
583         max_run_time = (integer)content;
584     else if ( (content = get_variable_value(to_parse, "gift")) != "")
585         gift_card_name = content;
586     else if ( (content = get_variable_value(to_parse, "maxrandoff")) != "")
587         obj_max_offset_add = (vector)content;
588     else if ( (content = get_variable_value(to_parse, "maxrandrot")) != "")
589         obj_max_rotation_add = (vector)content;
590     else if ( (content = get_variable_value(to_parse, "owner_only")) != "")
591         owner_only = (integer)content;
592     else if ( (content = get_variable_value(to_parse, "die_channel")) != "")
593         DIE_ON_DEMAND_CHANNEL = (integer)content;
594     else if ( (content = get_variable_value(to_parse, "perform_jaunts")) != "")
595         PERFORM_JAUNTS = (integer)content;
596 }
597
598 // examines the configuration to find one section of rezzable plan.
599 // returns TRUE when the entire config has been consumed.
600 integer read_one_section()
601 {
602     integer count = llGetListLength(global_config_list);
603     if (global_config_index >= count) return TRUE;  // all done.
604     integer sec_indy = global_config_index;
605     string line = llList2String(global_config_list, sec_indy);
606     // search for a section beginning.
607     if (llGetSubString(line, 0, 0) == "[") {
608         // we found the start of a section name.  now read the contents.
609         reset_variables_to_defaults();  // clean out former thoughts.
610         if (SUPER_NOISY_DEBUG)
611             log_it("reading section: " + llGetSubString(line, 1, -2));
612         // iterate across the items in current config section.
613         // initializer skips line we just read.
614         for (sec_indy++; sec_indy < count; sec_indy++) {
615             // read the lines in the section.
616             line = llList2String(global_config_list, sec_indy);
617             if (llGetSubString(line, 0, 0) != "[") {
618                 // try to interpret this line as a variable setting.
619                 parse_variable_definition(line);
620             } else {
621                 // done chowing on defs, so break out.
622                 global_config_index = sec_indy;
623                 // if the index is past the end, we're all done.
624                 return global_config_index >= count;
625             }
626         }
627         global_config_index = sec_indy;
628         if (SUPER_NOISY_DEBUG) log_it("read_one_section finished file, post loop.");
629         return TRUE;  // at end of list.
630     } else {
631         if ( (line != "") && !is_prefix(line, "#") ) {
632             if (SUPER_NOISY_DEBUG) log_it("skipping gibberish: " + line);
633             global_config_index++;
634             return FALSE;  // not done yet.
635         }
636     }
637
638     if (SUPER_NOISY_DEBUG) log_it("read_one_section finished file, bottom bailout.");
639     return TRUE;
640 }
641
642 // resets our variables to the default parameters.
643 reset_variables_to_defaults()
644 {
645     obj_name = "";
646     obj_offset = "<0.0, 0.0, 0.0>";
647     obj_position = <0.0, 0.0, 0.0>;
648     obj_rotation = "<0.0, 0.0, 0.0>";
649     obj_is_singleton = FALSE;
650     obj_multi_offset = "<0.0, 0.0, 0.0>";
651     obj_multi_angle = "<0.0, 0.0, 0.0>";
652     rezzed_at_once = 1;
653     obj_rezzing_period = 0.0;
654     max_run_time = 1200;
655     gift_card_name = "";
656     owner_only = FALSE;
657 }
658
659 integer completed_previous_section;  // had the last section finished processing?
660
661 integer finished_file = FALSE;  // has the config file been consumed?
662
663 // returns TRUE while it should continue to be called at the normal rate.
664 integer run_through_rez_plan()
665 {
666     if (jaunt_responses_awaited > 0) return TRUE;  // not ready yet.
667     if (sensor_checks_pending > 0) return TRUE;  // not ready yet.
668
669     if (completed_previous_section) {
670 //        if (SUPER_NOISY_DEBUG) log_it("decided to read config, prev sec complete now...");
671         // read in the next section of config.
672         finished_file = read_one_section();
673         // reset the current counters of rezzed items for the section.
674         current_count = 0;
675         current_object_index = 0;
676         recent_items_rezzed = 0;
677         already_done_items = [];
678     }
679     // now fire off the rez before we start the next section.
680     integer worked = perform_rezzing_action();
681     if (!worked) {
682         // we can't keep going; there needs to be a pause.
683 //        if (SUPER_NOISY_DEBUG) log_it("still working on previous section.");
684         completed_previous_section = FALSE;
685         return TRUE;
686     } else {
687 //        if (SUPER_NOISY_DEBUG) log_it("completed running previous section.");
688         completed_previous_section = TRUE;
689     }
690     
691     // check whether we rezzed anything at all, and if we're done.
692     if (finished_file) {
693 //log_it("seeing file as finished");
694         if (obj_rezzing_period == 0.0) {
695             // this one is not periodic, so we finish out its cycle.
696             if (DEBUGGING) log_it("shutting down rezzer; no objects left to create.");
697             is_enabled_now = FALSE;
698         }
699         global_config_index = 0;  // reset reading position for config.
700         attempt_to_go_home();  // try to go home after the run.
701         // periodic plans will just cause a snooze now.
702         return FALSE;
703     }
704     return TRUE;
705 }
706
707 // resets all our variables and starts reading the rez plan.
708 crank_rezzer_up()
709 {
710     is_enabled_now = TRUE;  // turn the device on.
711
712     main_home = llGetPos();  // set our home location now.
713     main_rotate = llGetRot();  // set our home rotation also.
714     when_last_enabled = llGetUnixTime();  // restart the run counter.
715     global_config_list = [];  // no config yet.
716     global_singleton_checker = "";  // we are not working on a sensor check for anything.
717     sensor_checks_pending = 0;  // no sensors are awaited.
718     recent_items_rezzed = 0;  // nothing rezzed recently.
719     jaunt_responses_awaited = 0;  // no jumps in progress.
720     rezzed_overall = 0;  // nothing has been rezzed yet.
721     gift_stack = [];  // no gifts waiting yet.
722
723     request_configuration();  // try to find a notecard and read our config.
724
725     // announce that we're open for business.
726     if (DEBUGGING) log_it("rezzer started... [free mem=" + (string)llGetFreeMemory() + "]");
727 }
728
729 //////////////
730 // from hufflets...
731 //
732 integer debug_num = 0;
733
734 // in the case of rezzeroni, we want to say things out loud for anyone
735 // to hear, since often the script is open to access by anyone.
736 // when debugging, we want to say to the owner also, so that problems can be removed.
737
738 // a debugging output method.  can be disabled entirely in one place.
739 log_it(string to_say)
740 {
741     debug_num++;
742     if (!SUPER_NOISY_DEBUG) {
743         // say this on open chat.
744         llSay(0, "[" + (string)debug_num + "] " + to_say);
745     } else {
746         // tell this to the owner.    
747         llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
748     }
749 }
750
751 // locates the string "text" in the list to "search_in".
752 integer find_in_list(list search_in, string text)
753
754     integer len = llGetListLength(search_in);
755     integer i; 
756     for (i = 0; i < len; i++) { 
757         if (llList2String(search_in, i) == text) 
758             return i; 
759     } 
760     return -1;
761 }
762
763 // returns TRUE if the "prefix" string is the first part of "compare_with".
764 integer is_prefix(string compare_with, string prefix)
765 { return (llSubStringIndex(compare_with, prefix) == 0); }
766
767 // returns a number at most "maximum" and at least "minimum".
768 // if "allow_negative" is TRUE, then the return may be positive or negative.
769 float randomize_within_range(float minimum, float maximum, integer allow_negative)
770 {
771     if (minimum > maximum) {
772         // flip the two if they are reversed.
773         float temp = minimum; minimum = maximum; maximum = temp;
774     }
775     float to_return = minimum + llFrand(maximum - minimum);
776     if (allow_negative) {
777         if (llFrand(1.0) < 0.5) to_return *= -1.0;
778     }
779     return to_return;
780 }
781
782 // returns a non-empty string if "to_check" defines a value for "variable_name".
783 // this must be in the form "X=Y", where X is the variable_name and Y is the value.
784 string get_variable_value(string to_check, string variable_name)
785 {
786     // clean initial spaces.
787     while (llGetSubString(to_check, 0, 0) == " ")
788         to_check = llDeleteSubString(to_check, 0, 0);
789     if (!is_prefix(to_check, variable_name)) return "";
790     to_check = llDeleteSubString(to_check, 0, llStringLength(variable_name) - 1);
791     // clean any spaces or valid assignment characters.
792     while ( (llGetSubString(to_check, 0, 0) == " ")
793             || (llGetSubString(to_check, 0, 0) == "=")
794             || (llGetSubString(to_check, 0, 0) == ",") )
795         to_check = llDeleteSubString(to_check, 0, 0);
796 //still too noisy...
797 //    if (SUPER_NOISY_DEBUG)
798 //        log_it("set '" + variable_name + "' = '" + to_check + "'");
799     string chewed_content = to_check;
800 //this is patching in CR.  shouldn't that be separate?
801     integer indy;
802     for (indy = 0; indy < llStringLength(chewed_content); indy++) {
803         if (llGetSubString(chewed_content, indy, indy) == "\\") {
804             if (llGetSubString(chewed_content, indy+1, indy+1) == "n") {
805                 chewed_content = llGetSubString(chewed_content, 0, indy - 1)
806                     + "\n"
807                     + llGetSubString(chewed_content, indy + 2,
808                         llStringLength(chewed_content) - 1);
809             }
810         }
811     }
812     // return what's left of the string.
813     return chewed_content;
814 }
815
816 // locates the item with "name_to_find" in the inventory items with the "type".
817 // a value from 0 to N-1 is returned if it's found, where N is the number of
818 // items in the inventory.
819 integer find_in_inventory(string name_to_find, integer inv_type)
820 {
821     integer num_inv = llGetInventoryNumber(inv_type);
822     if (num_inv == 0) return -1;  // nothing there!
823     integer inv;
824     for (inv = 0; inv < num_inv; inv++) {
825         if (llGetInventoryName(inv_type, inv) == name_to_find)
826             return inv;
827     }
828     return -2;  // failed to find it.
829 }
830
831 //////////////
832 // huffware script: auto-retire, by fred huffhines, version 2.5.
833 // distributed under BSD-like license.
834 //   !!  keep in mind that this code must be *copied* into another
835 //   !!  script that you wish to add auto-retirement capability to.
836 // when a script has auto_retire in it, it can be dropped into an
837 // object and the most recent version of the script will destroy
838 // all older versions.
839 //
840 // the version numbers are embedded into the script names themselves.
841 // the notation for versions uses a letter 'v', followed by two numbers
842 // in the form "major.minor".
843 // major and minor versions are implicitly considered as a floating point
844 // number that increases with each newer version of the script.  thus,
845 // "hazmap v0.1" might be the first script in the "hazmap" script continuum,
846 // and "hazmap v3.2" is a more recent version.
847 //
848 // example usage of the auto-retirement script:
849 //     default {
850 //         state_entry() {
851 //            auto_retire();  // make sure newest addition is only version of script.
852 //        }
853 //     }
854 // this script is partly based on the self-upgrading scripts from markov brodsky
855 // and jippen faddoul.
856 //////////////
857 auto_retire() {
858     string self = llGetScriptName();  // the name of this script.
859     list split = compute_basename_and_version(self);
860     if (llGetListLength(split) != 2) return;  // nothing to do for this script.
861     string basename = llList2String(split, 0);  // script name with no version attached.
862     string version_string = llList2String(split, 1);  // the version found.
863     integer posn;
864     // find any scripts that match the basename.  they are variants of this script.
865     for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
866 //log_it("invpo=" + (string)posn);
867         string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
868         if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
869             // found a basic match at least.
870             list inv_split = compute_basename_and_version(curr_script);
871             if (llGetListLength(inv_split) == 2) {
872                 // see if this script is more ancient.
873                 string inv_version_string = llList2String(inv_split, 1);  // the version found.
874                 // must make sure that the retiring script is completely the identical basename;
875                 // just matching in the front doesn't make it a relative.
876                 if ( (llList2String(inv_split, 0) == basename)
877                     && ((float)inv_version_string < (float)version_string) ) {
878                     // remove script with same name from inventory that has inferior version.
879                     llRemoveInventory(curr_script);
880                 }
881             }
882         }
883     }
884 }
885 //
886 // separates the base script name and version number.  used by auto_retire.
887 list compute_basename_and_version(string to_chop_up)
888 {
889     // minimum script name is 2 characters plus a version.
890     integer space_v_posn;
891     // find the last useful space and 'v' combo.
892     for (space_v_posn = llStringLength(to_chop_up) - 3;
893         (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
894         space_v_posn--) {
895         // look for space and v but do nothing else.
896 //log_it("pos=" + (string)space_v_posn);
897     }
898     if (space_v_posn < 2) return [];  // no space found.
899 //log_it("space v@" + (string)space_v_posn);
900     // now we zoom through the stuff after our beloved v character and find any evil
901     // space characters, which are most likely from SL having found a duplicate item
902     // name and not so helpfully renamed it for us.
903     integer indy;
904     for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
905 //log_it("indy=" + (string)space_v_posn);
906         if (llGetSubString(to_chop_up, indy, indy) == " ") {
907             // found one; zap it.  since we're going backwards we don't need to
908             // adjust the loop at all.
909             to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
910 //log_it("saw case of previously redundant item, aieee.  flattened: " + to_chop_up);
911         }
912     }
913     string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
914     // ditch the space character for our numerical check.
915     string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
916     // strip out a 'v' if there is one.
917     if (llGetSubString(chop_suffix, 0, 0) == "v")
918         chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
919     // if valid floating point number and greater than zero, that works for our version.
920     string basename = to_chop_up;  // script name with no version attached.
921     if ((float)chop_suffix > 0.0) {
922         // this is a big success right here.
923         basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
924         return [ basename, chop_suffix ];
925     }
926     // seems like we found nothing useful.
927     return [];
928 }
929 //
930 //////////////
931
932 // end hufflets
933 //////////////
934
935 integer trigger_stop_message = FALSE;
936     // a simple logging switch; first time in to awaiting commands,
937     // we do not want to say a run ended.  but every time after that,
938     // we do want to.
939
940 default {
941     state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
942     on_rez(integer parm) { state rerun; }
943 }
944 state rerun { state_entry() { state default; } }
945
946 state real_default
947 {
948     state_entry() {
949         auto_retire();
950         if (DEBUGGING) log_it("<state default>");
951         // we immediately jump into the command handling state.
952         // this state is really only for cleaning up older scripts with the auto-upgrade.
953         state awaiting_commands;
954     }
955 }
956
957 state awaiting_commands
958 {
959     state_entry() {
960         if (DEBUGGING) log_it("<state awaiting_commands>");
961         llSetTimerEvent(0.0);
962         if (trigger_stop_message)
963             if (DEBUGGING) log_it("rezzer finished.  [free mem=" + (string)llGetFreeMemory() + "]");
964         trigger_stop_message = TRUE;  // always say it next time.
965         reset_variables_to_defaults();  // clean out any old notes.
966         llListen(0, "", NULL_KEY, "");
967     }
968
969     touch_start(integer count) {
970         // see if we're blocking others right now.
971         if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
972         state reading_rez_plan;
973     }
974
975     // is someone speaking to the object?
976     listen(integer channel, string name, key id, string message) {
977         if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
978         if (handle_hearing_voices(channel, name, id, message)) state reading_rez_plan;
979     }
980 }
981
982 state reading_rez_plan
983 {
984     state_entry() {
985         if (DEBUGGING) log_it("<state reading_rez_plan>");
986         is_enabled_now = FALSE;
987
988 //        // we reset this list only here, since during periodic we'll keep resetting all
989 //        // our other variables.  this status must persist between runs though.
990 //        already_done_items = [];  // nothing's been done yet.
991
992         // reset our state so the rez plan will start being read.
993         completed_previous_section = TRUE;
994         finished_file = FALSE;
995
996         // get the timer cranking so we can process the configuration.
997         crank_rezzer_up();
998     }
999     
1000     // process the response from the noteworthy library.
1001     link_message(integer which, integer num, string msg, key id)
1002     {
1003         if (handle_link_message(which, num, msg, id))
1004             state running_rez_plan;
1005     }
1006
1007     touch_start(integer count) {}  // do nothing in this state, but keep touch event alive.
1008
1009 }
1010
1011 state running_rez_plan
1012 {
1013     state_entry() {
1014         if (DEBUGGING) log_it("<state running_rez_plan>");
1015         llListen(0, "", NULL_KEY, "");
1016         llSetTimerEvent(BASE_TIMER_PERIOD);
1017     }
1018     
1019     state_exit() { llSetTimerEvent(0); }
1020
1021     // we got clicked.
1022     touch_start(integer count) {
1023         if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
1024         // disable the rezzing process.
1025         is_enabled_now = FALSE;
1026         llSetTimerEvent(BASE_TIMER_PERIOD);
1027         // now we must allow the timer to handle the return process.
1028     }
1029
1030     // is someone speaking to the object?
1031     listen(integer channel, string name, key id, string message) {
1032         if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
1033         if (handle_hearing_voices(channel, name, id, message)) is_enabled_now = FALSE;
1034         llSetTimerEvent(BASE_TIMER_PERIOD);
1035     }
1036     
1037     timer() {
1038         if (handle_timer()) state awaiting_commands;
1039     }
1040
1041     object_rez(key id) {
1042         if (SUPER_NOISY_DEBUG) log_it("heard object rez: id=" + (string)id);
1043         if (!llGetListLength(gift_stack)) return;  // no gifts to hand out.
1044         string new_gift = llList2String(gift_stack, 0);
1045         gift_stack = llDeleteSubList(gift_stack, 0, 0);
1046         if (find_in_inventory(new_gift, INVENTORY_NOTECARD) < 0) {
1047             // missing the present.  this seems remarkable.
1048             log_it("could not find gift specified: " + new_gift);
1049             return;
1050         }
1051         llGiveInventory(id, new_gift);
1052     }
1053
1054     // process the response from the noteworthy library.
1055     link_message(integer which, integer num, string msg, key id)
1056     { handle_link_message(which, num, msg, id); }
1057
1058     // a sensor result came in--process either no match or the match.    
1059     no_sensor() { global_found_the_singleton = FALSE; sensor_checks_pending--; }
1060     sensor(integer num_detected) { global_found_the_singleton = TRUE; sensor_checks_pending--; }
1061 }