Merge branch 'dev' of git://feistymeow.org/feisty_meow into dev
[feisty_meow.git] / huffware / huffotronic_scripts / hufflets_v6.3.txt
1 
2 // huffware script: hufflets, by fred huffhines.
3 //
4 // functions that are commonly used but really are too simple to implement via IPC.
5 // these just get copied and pasted into other scripts.
6 // *note* that means you should not drop this script into an object.  it will not
7 // do anything for you.  instead, copy what you need out of here.
8 //
9 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
10 // do not use it in objects without fully realizing you are implicitly accepting that license.
11 //
12
13 integer DEBUGGING = FALSE;
14     // if this is set to true, then some functions produce noisier results.
15
16 //////////////
17
18 // diagnostic hufflets...
19
20 integer debug_num = 0;
21
22 // a debugging output method.  can be disabled entirely in one place.
23 log_it(string to_say)
24 {
25     debug_num++;
26     // tell this to the owner.    
27     llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
28     // say this on an unusual channel for chat if it's not intended for general public.
29 //    llSay(108, llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
30     // say this on open chat that anyone can hear.  we take off the bling for this one.
31 //    llSay(0, to_say);
32 }
33
34 //////////////
35
36 // mathematical hufflets...
37
38 // returns a floating point absolute value.
39 float fabs(float take_absolute_value) {
40     if (take_absolute_value >= 0.0) return take_absolute_value;
41     else return -1.0 * take_absolute_value;
42 }
43
44 //////////////
45
46 // time hufflets...
47
48 // shows a somewhat pretty printed version of the number of seconds.
49 string time_text(integer seconds)
50 {
51     float s_min = 60; float s_hour = 60 * s_min; float s_day = 24 * s_hour;
52     float s_year = 365.25 * s_day;
53     
54     if (seconds < s_min) return (string)seconds + " seconds";
55     else if (seconds < s_hour) return float_chop(seconds / s_min) + " minutes";
56     else if (seconds < s_day) return float_chop(seconds / s_hour) + " hours";
57     else if (seconds < s_year) return float_chop(seconds / s_day) + " days";
58     else return float_chop(seconds / s_year) + " years";
59 }
60
61 //////////////
62
63 // string processing hufflets...
64
65 // the string processing methods are not case sensitive.
66   
67 // returns TRUE if the "pattern" is found in the "full_string".
68 integer matches_substring(string full_string, string pattern)
69 { return (find_substring(full_string, pattern) >= 0); }
70
71 // returns the index of the first occurrence of "pattern" inside
72 // the "full_string".  if it is not found, then a negative number is returned.
73 integer find_substring(string full_string, string pattern)
74 { return llSubStringIndex(llToLower(full_string), llToLower(pattern)); }
75
76 // returns TRUE if the "prefix" string is the first part of "compare_with".
77 integer is_prefix(string compare_with, string prefix)
78 { return find_substring(compare_with, prefix) == 0; }
79
80 // takes any redundant space characters out of the string.
81 string compress_spaces(string s)
82 {
83     string to_return;
84     integer in_space = FALSE;
85     integer i;
86     for (i = 0; i < llStringLength(s); i++) {
87         string chunk = llGetSubString(s, i, i);
88         if (chunk == " ") {
89             if (in_space) {
90                 // we're have already seen a space.  don't keep this too.
91                 //continue;  no such keyword in lsl.
92             } else {
93                 in_space = TRUE;
94                 to_return += chunk;
95             }
96         } else {
97             // the current character was not a space, so just add it.
98             in_space = FALSE;
99             to_return += chunk;
100         }
101     }
102     return to_return;
103 }
104
105 //////////////
106
107 // sim-related hufflets...
108
109 // returns TRUE if the value in "to_check" specifies a legal x or y value in a sim.
110 integer valid_sim_value(float to_check)
111 {
112     if (to_check < 0.0) return FALSE;
113     if (to_check >= 257.0) return FALSE;
114     return TRUE;
115 }
116
117 // returns TRUE if the "to_check" vector is a location outside of the current sim.
118 integer outside_of_sim(vector to_check)
119 {
120     return !valid_sim_value(to_check.x) || !valid_sim_value(to_check.y);
121 }
122
123 //////////////
124
125 // list processing hufflets...
126
127 // locates the string "text" in the list to "search_in".
128 integer find_in_list(list search_in, string text)
129
130     integer len = llGetListLength(search_in);
131     integer i; 
132     for (i = 0; i < len; i++) { 
133         if (llList2String(search_in, i) == text) 
134             return i; 
135     } 
136     return -1;
137 }
138
139 // removes the entry at "index" and instead inserts the list "to_insert"
140 // at that position.
141 list replace_entry(list to_modify, integer index, list to_insert)
142 {
143     if (llGetListLength(to_modify) == 0) return to_insert;  // special case for empty.
144     return llListReplaceList(to_modify, to_insert, index, index);
145 }
146
147 // returns the portion of the list between start and end, but only if they are
148 // valid compared with the list length.  an attempt to use negative start or
149 // end values also returns a blank list.
150 list chop_list(list to_chop, integer start, integer end)
151 {
152     integer last_len = llGetListLength(to_chop) - 1;
153     if ( (start < 0) || (end < 0) || (start > last_len) || (end > last_len) ) return [];
154     return llList2List(to_chop, start, end);
155 }
156
157 //////////////
158
159 integer MAX_CHAT_LINE = 1008;
160     // the most characters we'll try to say in one chat.
161
162 dump_list_to_log(list to_show)
163 {
164     string text = dump_list(to_show);  // get some help from the other version.
165     integer len = llStringLength(text);
166     integer i;
167     for (i = 0; i < len; i += MAX_CHAT_LINE) {
168         integer last_bit = i + MAX_CHAT_LINE - 1;
169         if (last_bit >= len) last_bit = len - 1;
170         string next_line = llGetSubString(text, i, last_bit);
171         log_it(next_line);
172     }
173 }
174
175 // returns a printable form of the list.
176 string dump_list(list to_show)
177 {
178     integer len = llGetListLength(to_show);
179     integer i;
180     string text;
181     for (i = 0; i < len; i++) {
182         string next_line = llList2String(to_show, i);
183         if (find_substring(next_line, " ") >= 0) {
184             // this guy has a space in it, so quote it.
185             next_line = "'" + next_line + "'";
186         }
187         text = text + next_line;
188         if (i < len - 1) text = text + " ";
189     }
190     return text;
191 }
192
193 // extracts space separated elements from a string, and honors quoting of either
194 // variety as long as the quotes come in pairs.  this enables the inclusion of
195 // spaces in the elements of the set.  note that this function requires a well-formed
196 // string where there are no multiple space characters in a row.
197 list parse_quoted_strings(string to_parse)
198 {
199     list to_return;  // will pile up what we find in the string.
200     integer quoting = FALSE;  // are we inside quotes?
201     string curr_quote = "";  // what is current quote char, if any?
202     string accum;  // accumulates parts of the current element.
203     // loop over the string and apply our rules.
204     integer i;
205     for (i = 0; i < llStringLength(to_parse); i++) {
206         string c = llGetSubString(to_parse, i, i);
207         if (!quoting && (c == " ")) {
208             // this space marks the end of a word.
209             if (llStringLength(accum) > 0) {
210 //log_it("space adding to set: " + accum);
211                 to_return += [ accum ];
212                 accum = "";
213             }
214         } else if (quoting && (c == curr_quote)) {
215             // done with quotes, so add the quoted item, even if nil.
216             to_return += [ accum ];
217 //log_it("quote adding to set: " + accum);
218             accum = "";
219             quoting = FALSE;
220         } else if (!quoting && ( (c == "'") || (c == "\"") ) ) {
221             // we've started into quoting mode.
222             quoting = TRUE;
223             curr_quote = c;
224         } else {
225             // if no condition applies, just add this to the accumulator.
226             accum += c;
227         }
228     }
229     // add the last thing we accumulated.
230     if (llStringLength(accum) > 0) {
231 //log_it("last add to set: " + accum);
232         to_return += [ accum ];
233     }
234     return to_return;
235 }
236
237 //////////////
238
239 // action queue for postponed activities.  the first field held in a list item here
240 // is an integer action code.  the format of the remaining parameters is up to the
241 // caller, and they can be used as the final parameters for when the queued action
242 // gets handled.
243 list action_queue;
244
245 // looks at the action code at the head of the queue without removing the action.
246 integer peek_action_code()
247 {
248     list fields = llParseString2List(llList2String(action_queue, 0), [HUFFWARE_PARM_SEPARATOR], []);
249     return extract_action_code(fields);
250 }
251
252 // extracts the action code from a retrieved list.
253 integer extract_action_code(list to_parse) { return llList2Integer(to_parse, 0); }
254
255 // removes the current head of the action queue and returns it.
256 list pop_action_record()
257 {
258     if (llGetListLength(action_queue) == 0) {
259 //log_it("failure in action q: no entries.");
260         return [];
261     }
262     list top_action = llParseString2List(llList2String(action_queue, 0), [HUFFWARE_PARM_SEPARATOR], []);
263     action_queue = llDeleteSubList(action_queue, 0, 0);
264     return top_action;
265 }
266
267 // adds a new action to the end of the action queue.
268 push_action_record(integer action, list added_parms)
269 {
270     action_queue += [ wrap_parameters([action] + added_parms) ];
271 }
272
273 //////////////
274
275 // randomizing hufflets...
276
277 // returns a number at most "maximum" and at least "minimum".
278 // if "allow_negative" is TRUE, then the return may be positive or negative.
279 float randomize_within_range(float minimum, float maximum, integer allow_negative)
280 {
281     if (minimum > maximum) {
282         // flip the two if they are reversed.
283         float temp = minimum; minimum = maximum; maximum = temp;
284     }
285     float to_return = minimum + llFrand(maximum - minimum);
286     if (allow_negative) {
287         if (llFrand(1.0) < 0.5) to_return *= -1.0;
288     }
289     return to_return;
290 }
291
292 // returns a random vector where x,y,z will be between "minimums" and "maximums"
293 // x,y,z components.  if "allow_negative" is true, then any component will
294 // randomly be negative or positive.
295 vector random_bound_vector(vector minimums, vector maximums, integer allow_negative)
296 {
297     return <randomize_within_range(minimums.x, maximums.x, allow_negative),
298         randomize_within_range(minimums.y, maximums.y, allow_negative),
299         randomize_within_range(minimums.z, maximums.z, allow_negative)>;
300 }
301
302 // returns a vector whose components are between minimum and maximum.
303 // if allow_negative is true, then they can be either positive or negative.
304 vector random_vector(float minimum, float maximum, integer allow_negative)
305 {
306     return random_bound_vector(<minimum, minimum, minimum>,
307         <maximum, maximum, maximum>, allow_negative);
308 }
309
310 //////////////
311
312 // vector hufflets...
313
314 // returns TRUE if a is less than b in any component.
315 integer vector_less_than(vector a, vector b)
316 { return (a.x < b.x) || (a.y < b.y) || (a.z < b.z); }
317
318 // returns TRUE if a is greater than b in any component.
319 integer vector_greater_than(vector a, vector b)
320 { return (a.x > b.x) || (a.y > b.y) || (a.z > b.z); }
321
322 // returns text for a floating point number, but includes only
323 // three digits after the decimal point.
324 string float_chop(float to_show)
325 {
326     integer mant = llAbs(llRound(to_show * 1000.0) / 1000);
327     string neg_sign;
328     if (to_show < 0.0) neg_sign = "-";
329     string dec_s = (string)((llRound(to_show * 1000.0) - mant * 1000) / 1000.0);
330     dec_s = llGetSubString(llGetSubString(dec_s, find_substring(dec_s, ".") + 1, -1), 0, 2);
331     // strip off all trailing zeros.
332     while (llGetSubString(dec_s, -1, -1) == "0")
333         dec_s = llDeleteSubString(dec_s, -1, -1);
334     string to_return = neg_sign + (string)mant;
335     if (llStringLength(dec_s)) to_return += "." + dec_s;
336     return to_return;
337 }
338
339 // returns a prettier form for vector text, with chopped floats.
340 string vector_chop(vector to_show)
341 {
342     return "<" + float_chop(to_show.x) + ","
343         + float_chop(to_show.y) + ","
344         + float_chop(to_show.z) + ">";
345 }
346
347 // prints the list of vectors with trimmed floats.
348 string vector_list_chop(list to_show)
349 {
350     integer list_len = llGetListLength(to_show);
351     string to_return;
352     integer indy;
353     for (indy = 0; indy < list_len; indy++) {
354         if (indy != 0) to_return += HUFFWARE_ITEM_SEPARATOR;
355         to_return += vector_chop((vector)llList2String(to_show, indy));
356     }
357     return to_return;
358 }
359
360 // returns a list with two components; a new vector and a boolean.
361 // the new vector starts from "starting_point".  it will have a vector
362 // between "minimum_addition" and "maximum_addition" added to it.
363 // if it is over the "minimum_allowed" or the "maximum_allowed", then
364 // it is reset to whichever it would have crossed over.  two booleans
365 // are also returned to indicate when the lower and upper limits were
366 // exceeded (in that order).
367 list limit_and_add(vector starting_point,
368     vector minimum_allowed, vector maximum_allowed,
369     vector minimum_addition, vector maximum_addition)
370 {
371     integer too_low = FALSE;
372     integer too_high = FALSE;
373     vector new_location = starting_point;
374     vector addition = random_bound_vector(minimum_addition, maximum_addition, FALSE);
375 //log_it("start=" + (string)starting_point + " addin=" + (string)addition);
376     new_location += addition;
377     if (vector_less_than(new_location, minimum_allowed)) {
378         too_low = TRUE;
379         new_location = minimum_allowed;
380     } else if (vector_greater_than(new_location, maximum_allowed)) {
381         too_high = TRUE;
382         new_location = maximum_allowed;
383     }
384     return [ new_location, too_low, too_high ];
385 }
386
387 //////////////
388
389 // SL name hufflets...
390
391 // returns the position of the last space in "look_within" or a negative number.
392 integer find_last_space(string look_within)
393 {
394     integer indy = llStringLength(look_within) - 1;
395     while ( (indy >= 0) && (llGetSubString(look_within, indy, indy) != " ") ) indy--;
396     return indy;
397 }
398
399 // returns the first name for an avatar with the "full_name".
400 string first_name(string full_name)
401 {
402     integer finding = find_last_space(full_name);
403     if (finding >= 0) return llGetSubString(full_name, 0, finding - 1);
404     return full_name;  // failed to find space.
405 }
406
407 // returns the last name for an avatar with the "full_name".
408 string last_name(string full_name)
409 {
410     integer finding = find_last_space(full_name);
411     if (finding >= 0) return llGetSubString(full_name, finding + 1, -1);
412     return full_name;  // failed to find space.
413 }
414
415 //////////////
416
417 // variable handling hufflets...
418
419 // substitutes a variable's name for its value.  note that variables are assumed to start
420 // with a dollar sign character, which should not be provided in the "variable_name" parameter.
421 string substitute_variable(string substitute_within, string variable_name, string variable_value)
422 {
423     string to_return = substitute_within;
424 //log_it("before var sub: " + substitute_within);
425     integer posn;
426     while ( (posn = find_substring(to_return, "$" + variable_name)) >= 0) {
427         // we found an occurrence of the variable.
428         to_return = llDeleteSubString(to_return, posn, -1)  // keep part before.
429             + variable_value  // add the value in place of the variable name.
430             + llDeleteSubString(to_return, 0, posn + llStringLength(variable_name));
431                 // keep part after.
432     }
433 //log_it("after var sub: " + to_return);
434     return to_return;
435 }
436
437 // in "substitute_within", this finds any occurrences of items in the "variables_names"
438 // and replaces those with the corresponding item from "variable_values".
439 // note: this can be very stack intensive, so it might be better just to use multiple
440 // calls to the substitute_variable function.
441 string substitute_variable_list(string substitute_within, list variable_names, list variable_values)
442 {
443     string to_return = substitute_within;
444     integer vars = llGetListLength(variable_names);
445     if (vars != llGetListLength(variable_values)) {
446         log_it("error in substitute_variable_list: inequal number of variable names vs. values.");
447         return to_return;
448     }
449     integer indy;
450     for (indy = 0; indy < vars; indy++) {
451         to_return = substitute_variable(to_return,
452             llList2String(variable_names, indy),
453             llList2String(variable_values, indy));
454     }
455     return to_return;
456 }
457
458 // parses a variable definition to find the name of the variable and its value.
459 // this returns two strings [X, Y], if "to_split" is in the form X=Y.
460 list separate_variable_definition(string to_split)
461 {
462     integer equals_indy = llSubStringIndex(to_split, "=");
463     // we don't support missing an equals sign, and we don't support it as the first character.
464     if (equals_indy <= 0) return [];  // no match.
465     string x = llGetSubString(to_split, 0, equals_indy - 1);
466     string y = llGetSubString(to_split, equals_indy + 1, -1);
467     to_split = "";  // save space.
468     return [ llStringTrim(x, STRING_TRIM), llStringTrim(y, STRING_TRIM) ];
469 }
470
471 // returns a non-empty string if "to_check" defines a value for "variable_name".
472 // this must be in the form "X=Y", where X is the variable_name and Y is the value.
473 string get_variable_value(string to_check, string variable_name)
474 {
475     list x_y = separate_variable_definition(to_check);
476     if (llGetListLength(x_y) != 2) return "";  // failure to parse a variable def at all.
477     if (!is_prefix(llList2String(x_y, 0), variable_name)) return "";  // no match.
478     return llList2String(x_y, 1);  // a match!
479 }
480
481 //////////////
482
483 // inventory hufflets...
484
485 // locates the item with "name_to_find" in the inventory items with the "type".
486 // a value from 0 to N-1 is returned if it's found, where N is the number of
487 // items in the inventory.
488 integer find_in_inventory(string name_to_find, integer inv_type)
489 {
490     integer num_inv = llGetInventoryNumber(inv_type);
491     if (num_inv == 0) return -1;  // nothing there!
492     integer inv;
493     for (inv = 0; inv < num_inv; inv++) {
494         if (llGetInventoryName(inv_type, inv) == name_to_find)
495             return inv;
496     }
497     return -2;  // failed to find it.
498 }
499
500 // looks for an inventory item with the same prefix as the "basename_to_seek".
501 integer find_basename_in_inventory(string basename_to_seek, integer inv_type)
502 {
503     integer num_inv = llGetInventoryNumber(inv_type);
504     if (num_inv == 0) return -1;  // nothing there!
505     integer indy;
506     for (indy = 0; indy < num_inv; indy++) {
507         if (is_prefix(llGetInventoryName(inv_type, indy), basename_to_seek))
508             return indy;
509     }
510     return -2;  // failed to find it.
511 }
512
513 //////////////
514 //
515 // imported from auto-retire, which is the official source!...
516 //
517 // separates the base script name and version number.  used by auto_retire.
518 list compute_basename_and_version(string to_chop_up)
519 {
520     // minimum script name is 2 characters plus a version.
521     integer space_v_posn;
522     // find the last useful space and 'v' combo.
523     for (space_v_posn = llStringLength(to_chop_up) - 3;
524         (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
525         space_v_posn--) {
526         // look for space and v but do nothing else.
527     }
528     if (space_v_posn < 2) return [];  // no space found.
529     // now we zoom through the stuff after our beloved v character and find any evil
530     // space characters, which are most likely from SL having found a duplicate item
531     // name and not so helpfully renamed it for us.
532     integer indy;
533     for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
534         if (llGetSubString(to_chop_up, indy, indy) == " ") {
535             // found one; zap it.  since we're going backwards we don't need to
536             // adjust the loop at all.
537             to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
538         }
539     }
540     string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
541     // ditch the space character for our numerical check.
542     string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
543     // strip out a 'v' if there is one.
544     if (llGetSubString(chop_suffix, 0, 0) == "v")
545         chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
546     // if valid floating point number and greater than zero, that works for our version.
547     string basename = to_chop_up;  // script name with no version attached.
548     if ((float)chop_suffix > 0.0) {
549         // this is a big success right here.
550         basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
551         return [ basename, chop_suffix ];
552     }
553     // seems like we found nothing useful.
554     return [];
555 }
556 //
557 //////////////
558
559 // note that this new, lower memory version, depends on the inventory functions returning
560 // items in alphabetical order.
561 scrub_items_by_type(string this_guy, integer inventory_type)
562 {
563     list removal_list;
564     integer outer;
565     for (outer = 0; outer < llGetInventoryNumber(inventory_type); outer++) {
566         string curr = llGetInventoryName(inventory_type, outer);
567         list split = compute_basename_and_version(curr);
568         // make sure there was a comparable version number in this name.
569         if ( (curr != this_guy) && llGetListLength(split)) {
570             string curr_base = llList2String(split, 0);
571             float curr_ver = (float)llList2String(split, 1);
572 //log_it("outer: " + curr_base + " / " + (string)curr_ver);
573             integer inner;
574             for (inner = outer + 1; inner < llGetInventoryNumber(inventory_type); inner++) {
575                 string next_guy = llGetInventoryName(inventory_type, inner);
576                 list comp_split = compute_basename_and_version(next_guy);
577                 if (llGetListLength(comp_split)) {
578                     string comp_base = llList2String(comp_split, 0);
579                     float comp_ver = (float)llList2String(comp_split, 1);
580                     // okay, now we can actually compare.
581                     if (curr_base != comp_base) {
582                         // break out of inner loop.  we are past where the names can matter.
583                         inner = 2 * llGetInventoryNumber(inventory_type);
584                     } else {
585 //log_it("inner: " + comp_base + " / " + (string)comp_ver);
586                         if (curr_ver <= comp_ver) {
587                             // the script at inner index is comparable or better than
588                             // the script at the outer index.
589                             removal_list += curr;
590                         } else {
591                             // this inner script must be inferior to the outer one,
592                             // somehow, which defies our expectation of alphabetical ordering.
593                             removal_list += next_guy;
594                         }
595                     }
596                 }
597             }
598         }
599     }
600
601     // now actually do the deletions.
602     for (outer = 0; outer < llGetListLength(removal_list); outer++) {
603         string to_whack = llList2String(removal_list, outer);
604         log_it("removing older asset: " + to_whack);
605         llRemoveInventory(to_whack);
606     }
607 }
608
609 // ensures that only the latest version of any script or object is kept in our inventory.
610 destroy_older_versions()
611 {
612     // firstly, iterate across scripts to clean out older versions.
613     scrub_items_by_type(llGetScriptName(), INVENTORY_SCRIPT);
614     // secondly, try to clean out the objects.
615     scrub_items_by_type(llGetScriptName(), INVENTORY_OBJECT);
616 }
617
618 //////////////
619
620 // interprocess communication hufflets...  i.e., IPC parts.
621
622 // a repository for commonly used inter-process communication (IPC) source
623 // code.  hopefully in the future this will be worthwhile coming to first,
624 // when a new link message API is being built.
625
626 // an example link message API...
627 //////////////
628 integer SERVICE_X_HUFFWARE_ID = -14000;
629     // a unique ID within the huffware system for this script.
630 string HUFFWARE_PARM_SEPARATOR = "{~~~}";
631     // this pattern is an uncommon thing to see in text, so we use it to separate
632     // our commands in link messages.
633 string HUFFWARE_ITEM_SEPARATOR = "{|||}";
634     // used to separate lists of items from each other when stored inside a parameter.
635     // this allows lists to be passed as single string parameters if needed.
636 integer REPLY_DISTANCE = 100008;  // offset added to service's huffware id in reply IDs.
637 //////////////
638
639 // then the main body of IPC support functions.
640
641 // joins a list of parameters using the parameter sentinel for the library.
642 string wrap_parameters(list to_flatten)
643 { return llDumpList2String(to_flatten, HUFFWARE_PARM_SEPARATOR); }
644
645 // joins a list of sub-items using the item sentinel for the library.
646 string wrap_item_list(list to_wrap)
647 { return llDumpList2String(to_wrap, HUFFWARE_ITEM_SEPARATOR); }
648
649 // handles when blank strings need to come through the pipe.
650 string wrap_blank_string(string to_wrap)
651 {
652     if (llStringLength(to_wrap)) return to_wrap;  // that one is okay.
653     return "\"\"";  // return a quoted nothing as a signal for a blank.
654 }
655
656 // undoes a previously wrapped blank string.
657 string interpret_blank_string(string to_unwrap)
658 {
659     if (to_unwrap == "\"\"") return "";  // that was an encoded blank.
660     return to_unwrap;  // no encoding.
661 }
662
663 // a simple version of a reply for a command that has been executed.  the parameters
664 // might contain an outcome or result of the operation that was requested.
665 send_reply(integer destination, list parms, string command)
666 {
667     llMessageLinked(destination, SERVICE_X_HUFFWARE_ID + REPLY_DISTANCE, command,
668         llDumpList2String(parms, HUFFWARE_PARM_SEPARATOR));
669 }
670
671 // this should be invoked from the link_message event handler to process the requests
672 // for whatever service this library script provides.
673 handle_link_message(integer sender, integer huff_id, string msg, key id)
674 {
675 log_it("link msg: " + (string)sender + " " + (string)huff_id + " msg=" + msg + " id=" + (string)id);
676     // this check is more for the server; the server should listen on the main huffware id.
677     if (huff_id != SERVICE_X_HUFFWARE_ID) {
678         // the check below would make more sense in the client; it should listen on huffware id + REPLY_DISTANCE.
679         if (huff_id != SERVICE_X_HUFFWARE_ID + REPLY_DISTANCE) return;  // totally not for us.
680         // this is a reply to a previous request.
681         if (msg == "moobert") {
682             // handle the response.
683         }
684         // done with reply handling.
685         return;
686     }
687     // separate the list out
688     list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
689     
690     //example usage of parsed pieces.
691     key k = (key)llList2String(parms, 0);
692     string s = interpret_blank_string(llList2String(parms, 1));
693     integer i = (integer)llList2String(parms, 2);
694     vector v = (vector)llList2String(parms, 3);
695     
696     // we interpret the "msg" as a command.  the "id" has extra parameters.
697     if (msg == "unflux") {
698         // do something
699     }
700 }
701
702 //////////////
703
704 // graphical hufflets...
705
706 // a replacement for the deprecated llMakeExplosion function; this bangs the
707 // "texture" out as a particle with the "size" in meters.  the number of 
708 // particles in a burst is passed in "particle_count".  the function will
709 // generate a timer event after "timeout" seconds (pass zero for no timeout).
710 make_explosion(string texture, vector size, integer particle_count,
711     float timeout)
712 {
713     llParticleSystem([PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | PSYS_PART_EMISSIVE_MASK,
714         PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE,
715         PSYS_PART_START_SCALE, size,
716         PSYS_PART_END_SCALE, size,
717         PSYS_SRC_BURST_PART_COUNT, particle_count,
718         PSYS_PART_MAX_AGE, 2,
719         PSYS_SRC_TEXTURE, texture]);
720     llSetTimerEvent(timeout);
721 }
722 // the event handler for the particle system to be shut down again.
723 //timer() { llParticleSystem([]); }
724
725 //////////////
726
727 // the following state mumbo jumbo is so that our scripts do not go crazy when the huffotronic
728 // updater is rezzed.  that device has most of our scripts in it, and cannot always crush their
729 // state fast enough to stop them all.
730 default
731 {
732     state_entry() { if (llSubStringIndex(llGetObjectName(),  "huffotronic") < 0) state real_default; }
733     on_rez(integer parm) { state rerun; }
734 }
735 state rerun { state_entry() { state default; } }
736
737 state real_default
738 {
739     state_entry() {
740         log_it("memory left " + (string)llGetFreeMemory());
741     }
742 }