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