2 // huffware script: rezzeroni, by fred huffhines
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.
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.
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.
20 // global constants...
22 integer DEBUGGING = FALSE; // produces noisy diagnostics if enabled.
24 integer SUPER_NOISY_DEBUG = FALSE; // extraordinarily noisy items are logged.
26 float HOME_PROXIMITY = 0.1; // object must be this close to home before we quit.
28 integer PERFORM_JAUNTS = TRUE; // set to true if rezzer should teleport to rez locations.
30 integer OBJECT_STARTUP_PARM = 14042;
31 //hmmm: may want this to come from notecard someday.
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.
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.
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.
75 // the home location for the rezzer object. it will return to this after
76 // performing the requested rezzes.
78 // original rotation of the rezzer object.
80 integer jaunt_responses_awaited;
81 // the number of jumps still pending. should never be more than one in this script.
83 // constants that should generally not be modified unless you are an expert...
85 string REZZERONI_SIGNATURE = "#rezzeroni"; // the expected first line of our notecards.
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.
91 float BASE_TIMER_PERIOD = 0.04; // the rate at which the timer runs to rez objects.
93 // requires noteworthy library v9.3 or better.
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
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.
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); }
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)
136 //log_it("setting timer event for " + (string)timer_period + "s.");
137 llSetTimerEvent(0.0);
138 llSetTimerEvent(timer_period);
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()
147 global_notecard_name = "";
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,
157 // processes link messages received from support libraries.
158 integer handle_link_message(integer which, integer num, string msg, key id)
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;
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?
179 if ( (num != NOTEWORTHY_HUFFWARE_ID + REPLY_DISTANCE)
180 || (msg != READ_NOTECARD_COMMAND) ) return FALSE; // not for us.
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.
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.");
212 // requires jaunting library v10.5 or greater.
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.
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); }
232 // asks the jaunting library to take us to the target using a list of waypoints.
233 request_jaunt(list full_journey, integer forwards)
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);
243 // jaunts back to our home location.
246 // jump back to home.
247 request_jaunt([llGetPos(), main_home], TRUE);
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()
256 list objects_to_rez; // what items are we expected to create in this round?
258 integer num_inv = llGetInventoryNumber(INVENTORY_OBJECT);
260 log_it("Inventory is empty; please add objects to rez.");
261 set_timer(0.0); // stop the timer; nothing further to do.
264 //hmmm: all has never been tested.
265 if (obj_name == "ALL") {
267 for (inv = 0; inv < num_inv; inv++) {
268 objects_to_rez += [ llGetInventoryName(INVENTORY_OBJECT, inv) ];
270 obj_is_singleton = FALSE; // flag makes no sense for multiple objects.
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 + "').");
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.
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.");
293 if (obj_name != "") objects_to_rez += [ obj_name ];
297 return objects_to_rez;
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()
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.
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.");
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) {
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);
330 log_it("singleton item '" + obj_name + "' is absent; will rez one now if possible.");
336 // operates on the current parameters to rez an object or objects.
337 integer perform_rezzing_action()
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.");
349 // iterate across each object in inventory.
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);
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);
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);
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)
377 //log_it("home=" + (string)main_home + " rezplace=" + (string)rez_place);
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.
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;
392 // make sure we aren't already done with this object, if it's a singleton.
393 integer keep_going = validate_singleton_object();
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.
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.
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) {
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);
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.
416 // add in our rotation component as the rezzer.
417 real_rot *= main_rotate;
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];
435 global_singleton_checker = ""; // reset singleton checker for next object.
437 if (SUPER_NOISY_DEBUG) log_it("not rezzing, already saw it in list");
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 ];
450 current_object_index = 0; // reset the current object index.
457 // returns true if the state should change.
458 integer handle_timer()
460 // log_it("into handle timer.");
462 if (!is_enabled_now) {
463 if (jaunt_responses_awaited > 0) {
464 if (SUPER_NOISY_DEBUG) log_it("done rez, but jaunts awaited.");
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();
472 if (llGetListLength(gift_queue)) {
473 if (SUPER_NOISY_DEBUG) log_it("done rez, but still pending gifts.");
476 if (SUPER_NOISY_DEBUG) log_it("done rez, totally done.");
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;
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???");
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;
506 // even if the run were done, we still have to handle termination conditions.
510 // processes requests made by avatars via chat.
511 integer handle_hearing_voices(integer channel, string name, key id, string message)
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;
522 } else if ( (parm == "off") && is_enabled_now) {
523 is_enabled_now = FALSE;
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)
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);
544 return (vector)vector_or_not;
548 vector maybe_randomize_rotation(string vector_or_not, vector largest_add)
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);
555 return (vector)vector_or_not;
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)
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)>;
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)
573 string content; // filled after finding a variable name.
575 if ( (content = get_variable_value(to_parse, "name")) != "")
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;
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()
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);
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;
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.
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.
649 if (SUPER_NOISY_DEBUG) log_it("read_one_section finished file, bottom bailout.");
653 // resets our variables to the default parameters.
654 reset_variables_to_defaults()
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>";
664 obj_rezzing_period = 0.0;
670 integer completed_previous_section; // had the last section finished processing?
672 integer finished_file = FALSE; // has the config file been consumed?
674 // returns TRUE while it should continue to be called at the normal rate.
675 integer run_through_rez_plan()
677 if (jaunt_responses_awaited > 0) return TRUE; // not ready yet.
678 if (sensor_checks_pending > 0) return TRUE; // not ready yet.
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.
686 current_object_index = 0;
687 recent_items_rezzed = 0;
688 already_done_items = [];
690 // now fire off the rez before we start the next section.
691 integer worked = perform_rezzing_action();
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;
698 // if (SUPER_NOISY_DEBUG) log_it("completed running previous section.");
699 completed_previous_section = TRUE;
702 // check whether we rezzed anything at all, and if we're done.
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;
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.
718 // resets all our variables and starts reading the rez plan.
721 is_enabled_now = TRUE; // turn the device on.
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.
734 request_configuration(); // try to find a notecard and read our config.
736 // announce that we're open for business.
737 if (DEBUGGING) log_it("rezzer started... [free mem=" + (string)llGetFreeMemory() + "]");
743 integer debug_num = 0;
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.
749 // a debugging output method. can be disabled entirely in one place.
750 log_it(string to_say)
753 if (SUPER_NOISY_DEBUG) {
754 // say this on open chat.
755 llSay(0, llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
757 // tell this to just the owner.
758 llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
762 // locates the string "text" in the list to "search_in".
763 integer find_in_list(list search_in, string text)
765 integer len = llGetListLength(search_in);
767 for (i = 0; i < len; i++) {
768 if (llList2String(search_in, i) == text)
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); }
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)
782 if (minimum > maximum) {
783 // flip the two if they are reversed.
784 float temp = minimum; minimum = maximum; maximum = temp;
786 float to_return = minimum + llFrand(maximum - minimum);
787 if (allow_negative) {
788 if (llFrand(1.0) < 0.5) to_return *= -1.0;
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)
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);
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?
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)
818 + llGetSubString(chewed_content, indy + 2,
819 llStringLength(chewed_content) - 1);
823 // return what's left of the string.
824 return chewed_content;
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)
832 integer num_inv = llGetInventoryNumber(inv_type);
833 if (num_inv == 0) return -1; // nothing there!
835 for (inv = 0; inv < num_inv; inv++) {
836 if (llGetInventoryName(inv_type, inv) == name_to_find)
839 return -2; // failed to find it.
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.
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.
859 // example usage of the auto-retirement script:
862 // auto_retire(); // make sure newest addition is only version of script.
865 // this script is partly based on the self-upgrading scripts from markov brodsky
866 // and jippen faddoul.
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.
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);
897 // separates the base script name and version number. used by auto_retire.
898 list compute_basename_and_version(string to_chop_up)
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");
906 // look for space and v but do nothing else.
907 //log_it("pos=" + (string)space_v_posn);
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.
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);
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 ];
937 // seems like we found nothing useful.
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,
952 state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
953 on_rez(integer parm) { state rerun; }
955 state rerun { state_entry() { state default; } }
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;
968 state awaiting_commands
971 if (DEBUGGING) log_it("<state awaiting_commands>");
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, "");
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;
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;
993 state reading_rez_plan
996 if (DEBUGGING) log_it("<state reading_rez_plan>");
997 is_enabled_now = FALSE;
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.
1003 // reset our state so the rez plan will start being read.
1004 completed_previous_section = TRUE;
1005 finished_file = FALSE;
1007 // get the timer cranking so we can process the configuration.
1011 // process the response from the noteworthy library.
1012 link_message(integer which, integer num, string msg, key id)
1014 if (handle_link_message(which, num, msg, id))
1015 state running_rez_plan;
1018 touch_start(integer count) {} // do nothing in this state, but keep touch event alive.
1022 state running_rez_plan
1025 if (DEBUGGING) log_it("<state running_rez_plan>");
1026 llListen(0, "", NULL_KEY, "");
1027 set_timer(BASE_TIMER_PERIOD);
1030 state_exit() { set_timer(0); }
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.
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);
1049 if (handle_timer()) state awaiting_commands;
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);
1065 llGiveInventory(id, new_gift);
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); }
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--; }