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_stack; // a stack of items to hand to newly rezzed objects.
64 // somewhat ugly rez plan globals.
65 integer global_config_index; // where are we in the config list?
66 string global_singleton_checker; // is a singleton object being sensor checked?
67 integer global_found_the_singleton; // did we see the singleton object requested?
68 integer sensor_checks_pending; // how many sensor scans are awaited (zero or one).
69 integer recent_items_rezzed; // how many got rezzed in last run?
70 // object creation loop vars:
71 integer current_object_index = 0; // which object are we at, in the current section?
72 integer current_count; // the number already created of the current object.
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); }
131 // looks for a notecard with our signature in it and reads the configuration.
132 // an empty string is returned if we couldn't find anything.
133 request_configuration()
135 global_notecard_name = "";
138 // try to find a notecard with our configuration.
139 response_code = -1 * (integer)randomize_within_range(23, 80000, FALSE);
140 string parms_sent = wrap_parameters([REZZERONI_SIGNATURE, response_code]);
141 llMessageLinked(LINK_THIS, NOTEWORTHY_HUFFWARE_ID, READ_NOTECARD_COMMAND,
145 // processes link messages received from support libraries.
146 integer handle_link_message(integer which, integer num, string msg, key id)
149 if (num == JAUNT_HUFFWARE_ID + REPLY_DISTANCE) {
150 // did we get a response to a teleport request?
151 if (msg == JAUNT_LIST_COMMAND) {
152 //log_it("msg: " + (string)num + " str=" + str);
153 jaunt_responses_awaited--; // one less response being awaited.
154 if (jaunt_responses_awaited < 0) {
155 log_it("error, jaunts awaited < 0!");
156 jaunt_responses_awaited = 0;
159 parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
160 integer last_jaunt_was_success = llList2Integer(parms, 0);
161 vector posn = (vector)llList2String(parms, 1);
162 //log_it("got a reply for a jaunt request, success=" + (string)last_jaunt_was_success + " posn=" + (string)posn);
163 //do anything with the parms?
167 if ( (num != NOTEWORTHY_HUFFWARE_ID + REPLY_DISTANCE)
168 || (msg != READ_NOTECARD_COMMAND) ) return FALSE; // not for us.
170 // process the result of reading the notecard.
171 parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
172 string notecard_name = llList2String(parms, 0);
173 integer response_for = llList2Integer(parms, 1);
174 if (response_for != response_code) return FALSE; // oops, this isn't for us.
175 // make sure if we are being told to keep going.
176 if (notecard_name == NOTECARD_READ_CONTINUATION) {
177 //log_it("continuation of notecard seen.");
178 // snag all but the first two elements for our config now.
179 global_config_list += llList2List(parms, 2, -1);
180 // we're not done reading yet, so fall out to the false return.
181 } else if (notecard_name != "bad_notecard") {
182 // a valid notecard has been found and we're done with it now.
183 global_notecard_name = notecard_name;
184 // snag all but the first two elements for our config now.
185 global_config_list += llList2List(parms, 2, -1);
186 global_config_index = 0;
187 // signal that we're done getting the config.
190 // we hated the notecards we found, or there were none.
191 log_it("sorry, no notecards starting with '"
192 + REZZERONI_SIGNATURE
193 + "'; cannot rez yet.");
200 // requires jaunting library v10.5 or greater.
202 // do not redefine these constants.
203 integer JAUNT_HUFFWARE_ID = 10008;
204 // the unique id within the huffware system for the jaunt script to
205 // accept commands on. this is used in llMessageLinked as the num parameter.
206 // commands available via the jaunting library:
207 string JAUNT_COMMAND = "#jaunt#";
208 // command used to tell jaunt script to move object. pass a vector with the location.
209 string JAUNT_LIST_COMMAND = "#jauntlist#";
210 // like regular jaunt, but expects a list of vectors as the first parameter; this list
211 // should be in the jaunter notecard format (separated by pipe characters).
212 // the second parameter, if any, should be 1 for forwards traversal and 0 for backwards.
215 // encases a list of vectors in the expected character for the jaunting library.
216 string wrap_vector_list(list to_wrap)
217 { return llDumpList2String(to_wrap, HUFFWARE_ITEM_SEPARATOR); }
220 // asks the jaunting library to take us to the target using a list of waypoints.
221 request_jaunt(list full_journey, integer forwards)
223 if (PERFORM_JAUNTS) {
224 jaunt_responses_awaited++;
225 llMessageLinked(LINK_THIS, JAUNT_HUFFWARE_ID, JAUNT_LIST_COMMAND,
226 wrap_vector_list(full_journey)
227 + HUFFWARE_PARM_SEPARATOR + (string)forwards);
231 // jaunts back to our home location.
234 // jump back to home.
235 request_jaunt([llGetPos(), main_home], TRUE);
240 // creates the real list of items to rez based on the obj_name variable.
241 // this might turn out to be an empty list.
242 list build_rezzing_list()
244 list objects_to_rez; // what items are we expected to create in this round?
246 integer num_inv = llGetInventoryNumber(INVENTORY_OBJECT);
248 log_it("Inventory is empty; please add objects to rez.");
249 llSetTimerEvent(0.0); // stop the timer; nothing further to do.
252 //hmmm: all has never been tested.
253 if (obj_name == "ALL") {
255 for (inv = 0; inv < num_inv; inv++) {
256 objects_to_rez += [ llGetInventoryName(INVENTORY_OBJECT, inv) ];
258 obj_is_singleton = FALSE; // flag makes no sense for multiple objects.
260 // we know we are not being told to do a multiple set of objects.
261 if (obj_name == "random") {
262 // special keyword for a random object.
263 integer inv = (integer)randomize_within_range(0.0, num_inv, FALSE);
264 obj_name = llGetInventoryName(INVENTORY_OBJECT, inv);
265 if (SUPER_NOISY_DEBUG)
266 log_it("chose item #" + (string)inv + " randomly (called '" + obj_name + "').");
268 // a normal singular object to create.
269 if (find_in_inventory(obj_name, INVENTORY_OBJECT) < 0) {
270 // this item is bogus!
271 log_it("item '" + obj_name + "' is bogus! not in inventory.");
272 obj_name = ""; // reset it.
275 // now make sure we didn't already do this one.
276 if (find_in_list(already_done_items, obj_name) >= 0) {
277 // if (SUPER_NOISY_DEBUG)
278 // log_it("item '" + obj_name + "' has already been rezzed. skipping it.");
281 if (obj_name != "") objects_to_rez += [ obj_name ];
285 return objects_to_rez;
288 // checks whether the object is even supposed to be a singleton or not.
289 // if it should be a singleton, then we run a sensor ping to see if it exists in the
290 // vicinity or not. if it's not there, we'll eventually hear about it and create it.
291 integer validate_singleton_object()
293 // see if there's only supposed to be one of these objects.
294 if (!obj_is_singleton) return TRUE; // just keep going; it's not one.
295 if (obj_name == "") return TRUE; // this should never happen. pretend it's okay.
297 // okay, we know we should have a singleton. now did we already check for its presence?
298 if (global_singleton_checker == "") {
299 // have not scanned yet; postpone the rezzing action and start a sensor for the object.
300 sensor_checks_pending++;
301 global_singleton_checker = obj_name;
302 // maximally distant ping in full arc on all types of objects we can see.
303 llSensor(obj_name, NULL_KEY, AGENT|ACTIVE|PASSIVE, 96, PI);
304 if (DEBUGGING) log_it("scanning for singleton item '" + obj_name + "' now.");
307 // we should have finished a sensor probe now, since there were no checks pending
308 // and since this is a singleton.
309 if (global_found_the_singleton) {
311 log_it("singleton item '" + obj_name + "' already present; not re-rezzing.");
312 already_done_items += [ obj_name ];
313 //if (TERMINATE_IF_PRESENT)
314 string DIE_ON_DEMAND_MESSAGE = "die-on-demand";
315 llShout(DIE_ON_DEMAND_CHANNEL, DIE_ON_DEMAND_MESSAGE);
318 log_it("singleton item '" + obj_name + "' is absent; will rez one now if possible.");
324 // operates on the current parameters to rez an object or objects.
325 integer perform_rezzing_action()
327 vector current_home = llGetPos();
328 list objects_to_rez = build_rezzing_list();
329 //log_it("rezzing " + (string)llGetListLength(objects_to_rez) + " objects");
330 // create the specified number of the items we were told to use.
331 integer num_objects = llGetListLength(objects_to_rez);
332 if (num_objects == 0) {
333 // nothing to do at this point.
334 // if (SUPER_NOISY_DEBUG) log_it("no items in rez list; bailing out.");
337 // iterate across each object in inventory.
339 for (inv= current_object_index; inv < num_objects; inv++) {
340 //log_it("object index " + (string)inv);
341 // rez as many of the object as we were requested to.
342 string new_rezzee = llList2String(objects_to_rez, inv);
344 for (counter = current_count; counter < rezzed_at_once; counter++) {
345 // make sure we've never rezzed a non-periodic with this name before.
346 if (find_in_list(already_done_items, new_rezzee) < 0) {
347 ///log_it("haven't rezzed '" + new_rezzee + "' yet.");
348 vector rez_place = obj_position;
349 //log_it("configd rezplace=" + (string)rez_place);
350 if (rez_place == <0.0, 0.0, 0.0>) {
351 // they did not give us an absolute position, so use the home position.
352 rez_place = main_home;
353 //log_it("rehomed rezplace=" + (string)rez_place);
355 vector calc_offset = maybe_randomize_offset(obj_offset, obj_max_offset_add);
356 if (calc_offset != <0.0, 0.0, 0.0>) {
357 // the offset is non-zero, so apply it to the rez place.
358 rez_place += calc_offset * main_rotate;
359 //log_it("offsetd rezplace=" + (string)rez_place);
361 // add in the multiplier offset if this is not the first rezzing of this item.
362 rez_place += ((float)counter)
363 * maybe_randomize_offset(obj_multi_offset, obj_max_offset_add)
365 //log_it("home=" + (string)main_home + " rezplace=" + (string)rez_place);
367 // check that we are not too far away.
368 float rez_distance = llVecDist(rez_place, llGetPos());
369 if (rez_distance > MAXIMUM_REZ_DISTANCE) {
370 // try to jump there if we're too far away to rez from here.
372 log_it("distance is " + (string)rez_distance
373 + "; jaunting closer.");
374 request_jaunt([llGetPos(), rez_place], TRUE);
375 current_count = counter;
376 current_object_index = inv;
380 // make sure we aren't already done with this object, if it's a singleton.
381 integer keep_going = validate_singleton_object();
383 if (SUPER_NOISY_DEBUG) log_it("must sense for singleton, bailing.");
384 current_count = counter;
385 current_object_index = inv;
386 return FALSE; // we can't proceed yet. must check for singleton.
389 //hmmm: we need to offer an angular offset also!
390 // this would mean that the multiple guys could be set at nice different angles.
392 // now check again whether to rez this or not; the validation method adds it to the
393 // done list if the object is already present and is a singleton.
394 if (find_in_list(already_done_items, new_rezzee) < 0) {
396 log_it("now rezzing '" + new_rezzee + "', rot " + obj_rotation);
397 vector rota = maybe_randomize_rotation(obj_rotation, obj_max_rotation_add);
398 rotation real_rot = llEuler2Rot(rota * DEG_TO_RAD);
400 //hmmm: might be nice to offer an absolute version!
401 // that accounts for the specific position and offset, rather
402 // than relative to rezzer. angle should be kept absolutist also.
404 // add in our rotation component as the rezzer.
405 real_rot *= main_rotate;
407 // add in the angular addition for multiple object creation.
408 real_rot *= llEuler2Rot(
409 maybe_randomize_rotation(obj_multi_angle, obj_max_rotation_add)
410 * (float)counter * DEG_TO_RAD);
411 recent_items_rezzed++; // we got to do something.
412 // rez it up since we know what is wanted and where, plus we're in range.
413 llRezObject(new_rezzee, rez_place, ZERO_VECTOR, real_rot, OBJECT_STARTUP_PARM);
414 rezzed_overall++; // one more item.
415 if (gift_card_name != "") {
416 // this guy needs its startup gift.
417 string inv_name = gift_card_name + (string)rezzed_overall;
418 if (find_in_inventory(inv_name, INVENTORY_NOTECARD) >= 0) {
419 // this inventory item exists, so use it.
420 gift_stack += [ inv_name];
423 global_singleton_checker = ""; // reset singleton checker for next object.
425 if (SUPER_NOISY_DEBUG) log_it("not rezzing, already saw it in list");
429 current_count = 0; // got to the full count for the object.
430 // if this is a non-periodic object, we don't need any more runs.
431 if (obj_rezzing_period == 0.0) {
432 if (find_in_list(already_done_items, new_rezzee) < 0) {
433 if (SUPER_NOISY_DEBUG) log_it("adding newly done: " + (string)new_rezzee);
434 already_done_items += [ new_rezzee ];
438 current_object_index = 0; // reset the current object index.
445 // returns true if the state should change.
446 integer handle_timer()
448 // make sure we run at the fast rate; we'll reset the rate elsewhere if needed.
449 llSetTimerEvent(BASE_TIMER_PERIOD);
451 if (!is_enabled_now) {
452 if (jaunt_responses_awaited > 0) {
453 if (SUPER_NOISY_DEBUG) log_it("done rez, but jaunts awaited.");
456 if (llVecDist(llGetPos(), main_home) > HOME_PROXIMITY) {
457 if (SUPER_NOISY_DEBUG) log_it("done rez, but not at home.");
458 attempt_to_go_home();
461 if (llGetListLength(gift_stack)) {
462 if (SUPER_NOISY_DEBUG) log_it("done rez, but still pending gifts.");
465 if (SUPER_NOISY_DEBUG) log_it("done rez, totally done.");
466 llSetTimerEvent(0.0);
470 // check whether the max run time has elapsed.
471 integer run_time = llAbs(llGetUnixTime() - when_last_enabled);
472 if (run_time > max_run_time) {
473 log_it("Maximum run time elapsed; shutting down.");
474 is_enabled_now = FALSE;
475 llSetTimerEvent(0.0);
479 // we have not gotten a configuration yet.
480 if (global_notecard_name == "") {
481 /// if (SUPER_NOISY_DEBUG) log_it("in timer with no notecard???");
485 // crank to next step in config.
486 if (!run_through_rez_plan()) {
487 //log_it("told done with rez plan");
488 // we were told not to keep going. if this is a periodic run,
489 // then we'll snooze until the next time.
490 if (obj_rezzing_period != 0.0) {
491 llSetTimerEvent(obj_rezzing_period);
492 finished_file = FALSE;
495 // even if the run were done, we still have to handle termination conditions.
499 // processes requests made by avatars via chat.
500 integer handle_hearing_voices(integer channel, string name, key id, string message)
502 string rez_command = "#rez";
503 // is this our command prefix?
504 if (is_prefix(message, rez_command)) {
505 // we found a command. which specific one?
506 string parm = llDeleteSubString(message, 0, llStringLength(rez_command));
507 // eat the command portion, plus a space.
508 if ( (parm == "on") && !is_enabled_now) {
509 is_enabled_now = TRUE;
511 } else if ( (parm == "off") && is_enabled_now) {
512 is_enabled_now = FALSE;
519 // a helper function that turns the word "random" into a random value.
520 // we will never allow a negative z value for the random amount, since that will often be
521 // wrong (rezzing underneath where the object lives, e.g.). this implies always
522 // putting the rezzer below where one wants to see the random rezzing action.
523 vector maybe_randomize_offset(string vector_or_not, vector largest_add)
525 if (vector_or_not == "random") {
526 vector just_xy = <largest_add.x, largest_add.y, 0.0>;
527 vector just_z = <0.0, 0.0, largest_add.z>;
528 vector to_return = random_bound_vector(<0.0, 0.0, 0.0>, just_xy, TRUE)
529 + random_bound_vector(<0.0, 0.0, 0.0>, just_z, FALSE);
530 //log_it("calc rand off: " + (string)to_return);
533 return (vector)vector_or_not;
537 vector maybe_randomize_rotation(string vector_or_not, vector largest_add)
539 if (vector_or_not == "random") {
540 vector to_return = random_bound_vector(<0.0, 0.0, 0.0>, largest_add, TRUE);
541 //log_it("calc rand rot: " + (string)to_return);
544 return (vector)vector_or_not;
548 // returns a random vector where x,y,z will be between "minimums" and "maximums"
549 // x,y,z components. if "allow_negative" is true, then any component will
550 // randomly be negative or positive.
551 vector random_bound_vector(vector minimums, vector maximums, integer allow_negative)
553 return <randomize_within_range(minimums.x, maximums.x, allow_negative),
554 randomize_within_range(minimums.y, maximums.y, allow_negative),
555 randomize_within_range(minimums.z, maximums.z, allow_negative)>;
558 // consumes the notecard in a very application specific way
559 // to retrieve the configurations for the items to rez.
560 parse_variable_definition(string to_parse)
562 string content; // filled after finding a variable name.
564 if ( (content = get_variable_value(to_parse, "name")) != "")
566 else if ( (content = get_variable_value(to_parse, "position")) != "")
567 obj_position = (vector)content;
568 else if ( (content = get_variable_value(to_parse, "offset")) != "")
569 obj_offset = content;
570 else if ( (content = get_variable_value(to_parse, "rotation")) != "")
571 obj_rotation = content;
572 else if ( (content = get_variable_value(to_parse, "multi_offset")) != "")
573 obj_multi_offset = content;
574 else if ( (content = get_variable_value(to_parse, "multi_angle")) != "")
575 obj_multi_angle = content;
576 else if ( (content = get_variable_value(to_parse, "singleton")) != "")
577 obj_is_singleton = (integer)content;
578 else if ( (content = get_variable_value(to_parse, "count")) != "")
579 rezzed_at_once = (integer)content;
580 else if ( (content = get_variable_value(to_parse, "rate")) != "")
581 obj_rezzing_period = (float)content;
582 else if ( (content = get_variable_value(to_parse, "max_run")) != "")
583 max_run_time = (integer)content;
584 else if ( (content = get_variable_value(to_parse, "gift")) != "")
585 gift_card_name = content;
586 else if ( (content = get_variable_value(to_parse, "maxrandoff")) != "")
587 obj_max_offset_add = (vector)content;
588 else if ( (content = get_variable_value(to_parse, "maxrandrot")) != "")
589 obj_max_rotation_add = (vector)content;
590 else if ( (content = get_variable_value(to_parse, "owner_only")) != "")
591 owner_only = (integer)content;
592 else if ( (content = get_variable_value(to_parse, "die_channel")) != "")
593 DIE_ON_DEMAND_CHANNEL = (integer)content;
594 else if ( (content = get_variable_value(to_parse, "perform_jaunts")) != "")
595 PERFORM_JAUNTS = (integer)content;
598 // examines the configuration to find one section of rezzable plan.
599 // returns TRUE when the entire config has been consumed.
600 integer read_one_section()
602 integer count = llGetListLength(global_config_list);
603 if (global_config_index >= count) return TRUE; // all done.
604 integer sec_indy = global_config_index;
605 string line = llList2String(global_config_list, sec_indy);
606 // search for a section beginning.
607 if (llGetSubString(line, 0, 0) == "[") {
608 // we found the start of a section name. now read the contents.
609 reset_variables_to_defaults(); // clean out former thoughts.
610 if (SUPER_NOISY_DEBUG)
611 log_it("reading section: " + llGetSubString(line, 1, -2));
612 // iterate across the items in current config section.
613 // initializer skips line we just read.
614 for (sec_indy++; sec_indy < count; sec_indy++) {
615 // read the lines in the section.
616 line = llList2String(global_config_list, sec_indy);
617 if (llGetSubString(line, 0, 0) != "[") {
618 // try to interpret this line as a variable setting.
619 parse_variable_definition(line);
621 // done chowing on defs, so break out.
622 global_config_index = sec_indy;
623 // if the index is past the end, we're all done.
624 return global_config_index >= count;
627 global_config_index = sec_indy;
628 if (SUPER_NOISY_DEBUG) log_it("read_one_section finished file, post loop.");
629 return TRUE; // at end of list.
631 if ( (line != "") && !is_prefix(line, "#") ) {
632 if (SUPER_NOISY_DEBUG) log_it("skipping gibberish: " + line);
633 global_config_index++;
634 return FALSE; // not done yet.
638 if (SUPER_NOISY_DEBUG) log_it("read_one_section finished file, bottom bailout.");
642 // resets our variables to the default parameters.
643 reset_variables_to_defaults()
646 obj_offset = "<0.0, 0.0, 0.0>";
647 obj_position = <0.0, 0.0, 0.0>;
648 obj_rotation = "<0.0, 0.0, 0.0>";
649 obj_is_singleton = FALSE;
650 obj_multi_offset = "<0.0, 0.0, 0.0>";
651 obj_multi_angle = "<0.0, 0.0, 0.0>";
653 obj_rezzing_period = 0.0;
659 integer completed_previous_section; // had the last section finished processing?
661 integer finished_file = FALSE; // has the config file been consumed?
663 // returns TRUE while it should continue to be called at the normal rate.
664 integer run_through_rez_plan()
666 if (jaunt_responses_awaited > 0) return TRUE; // not ready yet.
667 if (sensor_checks_pending > 0) return TRUE; // not ready yet.
669 if (completed_previous_section) {
670 // if (SUPER_NOISY_DEBUG) log_it("decided to read config, prev sec complete now...");
671 // read in the next section of config.
672 finished_file = read_one_section();
673 // reset the current counters of rezzed items for the section.
675 current_object_index = 0;
676 recent_items_rezzed = 0;
677 already_done_items = [];
679 // now fire off the rez before we start the next section.
680 integer worked = perform_rezzing_action();
682 // we can't keep going; there needs to be a pause.
683 // if (SUPER_NOISY_DEBUG) log_it("still working on previous section.");
684 completed_previous_section = FALSE;
687 // if (SUPER_NOISY_DEBUG) log_it("completed running previous section.");
688 completed_previous_section = TRUE;
691 // check whether we rezzed anything at all, and if we're done.
693 //log_it("seeing file as finished");
694 if (obj_rezzing_period == 0.0) {
695 // this one is not periodic, so we finish out its cycle.
696 if (DEBUGGING) log_it("shutting down rezzer; no objects left to create.");
697 is_enabled_now = FALSE;
699 global_config_index = 0; // reset reading position for config.
700 attempt_to_go_home(); // try to go home after the run.
701 // periodic plans will just cause a snooze now.
707 // resets all our variables and starts reading the rez plan.
710 is_enabled_now = TRUE; // turn the device on.
712 main_home = llGetPos(); // set our home location now.
713 main_rotate = llGetRot(); // set our home rotation also.
714 when_last_enabled = llGetUnixTime(); // restart the run counter.
715 global_config_list = []; // no config yet.
716 global_singleton_checker = ""; // we are not working on a sensor check for anything.
717 sensor_checks_pending = 0; // no sensors are awaited.
718 recent_items_rezzed = 0; // nothing rezzed recently.
719 jaunt_responses_awaited = 0; // no jumps in progress.
720 rezzed_overall = 0; // nothing has been rezzed yet.
721 gift_stack = []; // no gifts waiting yet.
723 request_configuration(); // try to find a notecard and read our config.
725 // announce that we're open for business.
726 if (DEBUGGING) log_it("rezzer started... [free mem=" + (string)llGetFreeMemory() + "]");
732 integer debug_num = 0;
734 // in the case of rezzeroni, we want to say things out loud for anyone
735 // to hear, since often the script is open to access by anyone.
736 // when debugging, we want to say to the owner also, so that problems can be removed.
738 // a debugging output method. can be disabled entirely in one place.
739 log_it(string to_say)
742 if (!SUPER_NOISY_DEBUG) {
743 // say this on open chat.
744 llSay(0, "[" + (string)debug_num + "] " + to_say);
746 // tell this to the owner.
747 llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
751 // locates the string "text" in the list to "search_in".
752 integer find_in_list(list search_in, string text)
754 integer len = llGetListLength(search_in);
756 for (i = 0; i < len; i++) {
757 if (llList2String(search_in, i) == text)
763 // returns TRUE if the "prefix" string is the first part of "compare_with".
764 integer is_prefix(string compare_with, string prefix)
765 { return (llSubStringIndex(compare_with, prefix) == 0); }
767 // returns a number at most "maximum" and at least "minimum".
768 // if "allow_negative" is TRUE, then the return may be positive or negative.
769 float randomize_within_range(float minimum, float maximum, integer allow_negative)
771 if (minimum > maximum) {
772 // flip the two if they are reversed.
773 float temp = minimum; minimum = maximum; maximum = temp;
775 float to_return = minimum + llFrand(maximum - minimum);
776 if (allow_negative) {
777 if (llFrand(1.0) < 0.5) to_return *= -1.0;
782 // returns a non-empty string if "to_check" defines a value for "variable_name".
783 // this must be in the form "X=Y", where X is the variable_name and Y is the value.
784 string get_variable_value(string to_check, string variable_name)
786 // clean initial spaces.
787 while (llGetSubString(to_check, 0, 0) == " ")
788 to_check = llDeleteSubString(to_check, 0, 0);
789 if (!is_prefix(to_check, variable_name)) return "";
790 to_check = llDeleteSubString(to_check, 0, llStringLength(variable_name) - 1);
791 // clean any spaces or valid assignment characters.
792 while ( (llGetSubString(to_check, 0, 0) == " ")
793 || (llGetSubString(to_check, 0, 0) == "=")
794 || (llGetSubString(to_check, 0, 0) == ",") )
795 to_check = llDeleteSubString(to_check, 0, 0);
797 // if (SUPER_NOISY_DEBUG)
798 // log_it("set '" + variable_name + "' = '" + to_check + "'");
799 string chewed_content = to_check;
800 //this is patching in CR. shouldn't that be separate?
802 for (indy = 0; indy < llStringLength(chewed_content); indy++) {
803 if (llGetSubString(chewed_content, indy, indy) == "\\") {
804 if (llGetSubString(chewed_content, indy+1, indy+1) == "n") {
805 chewed_content = llGetSubString(chewed_content, 0, indy - 1)
807 + llGetSubString(chewed_content, indy + 2,
808 llStringLength(chewed_content) - 1);
812 // return what's left of the string.
813 return chewed_content;
816 // locates the item with "name_to_find" in the inventory items with the "type".
817 // a value from 0 to N-1 is returned if it's found, where N is the number of
818 // items in the inventory.
819 integer find_in_inventory(string name_to_find, integer inv_type)
821 integer num_inv = llGetInventoryNumber(inv_type);
822 if (num_inv == 0) return -1; // nothing there!
824 for (inv = 0; inv < num_inv; inv++) {
825 if (llGetInventoryName(inv_type, inv) == name_to_find)
828 return -2; // failed to find it.
832 // huffware script: auto-retire, by fred huffhines, version 2.5.
833 // distributed under BSD-like license.
834 // !! keep in mind that this code must be *copied* into another
835 // !! script that you wish to add auto-retirement capability to.
836 // when a script has auto_retire in it, it can be dropped into an
837 // object and the most recent version of the script will destroy
838 // all older versions.
840 // the version numbers are embedded into the script names themselves.
841 // the notation for versions uses a letter 'v', followed by two numbers
842 // in the form "major.minor".
843 // major and minor versions are implicitly considered as a floating point
844 // number that increases with each newer version of the script. thus,
845 // "hazmap v0.1" might be the first script in the "hazmap" script continuum,
846 // and "hazmap v3.2" is a more recent version.
848 // example usage of the auto-retirement script:
851 // auto_retire(); // make sure newest addition is only version of script.
854 // this script is partly based on the self-upgrading scripts from markov brodsky
855 // and jippen faddoul.
858 string self = llGetScriptName(); // the name of this script.
859 list split = compute_basename_and_version(self);
860 if (llGetListLength(split) != 2) return; // nothing to do for this script.
861 string basename = llList2String(split, 0); // script name with no version attached.
862 string version_string = llList2String(split, 1); // the version found.
864 // find any scripts that match the basename. they are variants of this script.
865 for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
866 //log_it("invpo=" + (string)posn);
867 string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
868 if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
869 // found a basic match at least.
870 list inv_split = compute_basename_and_version(curr_script);
871 if (llGetListLength(inv_split) == 2) {
872 // see if this script is more ancient.
873 string inv_version_string = llList2String(inv_split, 1); // the version found.
874 // must make sure that the retiring script is completely the identical basename;
875 // just matching in the front doesn't make it a relative.
876 if ( (llList2String(inv_split, 0) == basename)
877 && ((float)inv_version_string < (float)version_string) ) {
878 // remove script with same name from inventory that has inferior version.
879 llRemoveInventory(curr_script);
886 // separates the base script name and version number. used by auto_retire.
887 list compute_basename_and_version(string to_chop_up)
889 // minimum script name is 2 characters plus a version.
890 integer space_v_posn;
891 // find the last useful space and 'v' combo.
892 for (space_v_posn = llStringLength(to_chop_up) - 3;
893 (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
895 // look for space and v but do nothing else.
896 //log_it("pos=" + (string)space_v_posn);
898 if (space_v_posn < 2) return []; // no space found.
899 //log_it("space v@" + (string)space_v_posn);
900 // now we zoom through the stuff after our beloved v character and find any evil
901 // space characters, which are most likely from SL having found a duplicate item
902 // name and not so helpfully renamed it for us.
904 for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
905 //log_it("indy=" + (string)space_v_posn);
906 if (llGetSubString(to_chop_up, indy, indy) == " ") {
907 // found one; zap it. since we're going backwards we don't need to
908 // adjust the loop at all.
909 to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
910 //log_it("saw case of previously redundant item, aieee. flattened: " + to_chop_up);
913 string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
914 // ditch the space character for our numerical check.
915 string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
916 // strip out a 'v' if there is one.
917 if (llGetSubString(chop_suffix, 0, 0) == "v")
918 chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
919 // if valid floating point number and greater than zero, that works for our version.
920 string basename = to_chop_up; // script name with no version attached.
921 if ((float)chop_suffix > 0.0) {
922 // this is a big success right here.
923 basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
924 return [ basename, chop_suffix ];
926 // seems like we found nothing useful.
935 integer trigger_stop_message = FALSE;
936 // a simple logging switch; first time in to awaiting commands,
937 // we do not want to say a run ended. but every time after that,
941 state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
942 on_rez(integer parm) { state rerun; }
944 state rerun { state_entry() { state default; } }
950 if (DEBUGGING) log_it("<state default>");
951 // we immediately jump into the command handling state.
952 // this state is really only for cleaning up older scripts with the auto-upgrade.
953 state awaiting_commands;
957 state awaiting_commands
960 if (DEBUGGING) log_it("<state awaiting_commands>");
961 llSetTimerEvent(0.0);
962 if (trigger_stop_message)
963 if (DEBUGGING) log_it("rezzer finished. [free mem=" + (string)llGetFreeMemory() + "]");
964 trigger_stop_message = TRUE; // always say it next time.
965 reset_variables_to_defaults(); // clean out any old notes.
966 llListen(0, "", NULL_KEY, "");
969 touch_start(integer count) {
970 // see if we're blocking others right now.
971 if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
972 state reading_rez_plan;
975 // is someone speaking to the object?
976 listen(integer channel, string name, key id, string message) {
977 if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
978 if (handle_hearing_voices(channel, name, id, message)) state reading_rez_plan;
982 state reading_rez_plan
985 if (DEBUGGING) log_it("<state reading_rez_plan>");
986 is_enabled_now = FALSE;
988 // // we reset this list only here, since during periodic we'll keep resetting all
989 // // our other variables. this status must persist between runs though.
990 // already_done_items = []; // nothing's been done yet.
992 // reset our state so the rez plan will start being read.
993 completed_previous_section = TRUE;
994 finished_file = FALSE;
996 // get the timer cranking so we can process the configuration.
1000 // process the response from the noteworthy library.
1001 link_message(integer which, integer num, string msg, key id)
1003 if (handle_link_message(which, num, msg, id))
1004 state running_rez_plan;
1007 touch_start(integer count) {} // do nothing in this state, but keep touch event alive.
1011 state running_rez_plan
1014 if (DEBUGGING) log_it("<state running_rez_plan>");
1015 llListen(0, "", NULL_KEY, "");
1016 llSetTimerEvent(BASE_TIMER_PERIOD);
1019 state_exit() { llSetTimerEvent(0); }
1022 touch_start(integer count) {
1023 if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
1024 // disable the rezzing process.
1025 is_enabled_now = FALSE;
1026 llSetTimerEvent(BASE_TIMER_PERIOD);
1027 // now we must allow the timer to handle the return process.
1030 // is someone speaking to the object?
1031 listen(integer channel, string name, key id, string message) {
1032 if (owner_only && (llDetectedKey(0) != llGetOwner())) return;
1033 if (handle_hearing_voices(channel, name, id, message)) is_enabled_now = FALSE;
1034 llSetTimerEvent(BASE_TIMER_PERIOD);
1038 if (handle_timer()) state awaiting_commands;
1041 object_rez(key id) {
1042 if (SUPER_NOISY_DEBUG) log_it("heard object rez: id=" + (string)id);
1043 if (!llGetListLength(gift_stack)) return; // no gifts to hand out.
1044 string new_gift = llList2String(gift_stack, 0);
1045 gift_stack = llDeleteSubList(gift_stack, 0, 0);
1046 if (find_in_inventory(new_gift, INVENTORY_NOTECARD) < 0) {
1047 // missing the present. this seems remarkable.
1048 log_it("could not find gift specified: " + new_gift);
1051 llGiveInventory(id, new_gift);
1054 // process the response from the noteworthy library.
1055 link_message(integer which, integer num, string msg, key id)
1056 { handle_link_message(which, num, msg, id); }
1058 // a sensor result came in--process either no match or the match.
1059 no_sensor() { global_found_the_singleton = FALSE; sensor_checks_pending--; }
1060 sensor(integer num_detected) { global_found_the_singleton = TRUE; sensor_checks_pending--; }