2 // huffware script: "party culiar", by fred huffhines
4 // this is yet another particle system script, based on ideas seen
5 // in several scripts by various authors and assisted by the lsl wiki.
6 // it has the useful characteristic of being able to load its parameters
7 // from a notecard, thus making script modifications for the particle
9 // on initial rez, if a notecard exists, then it is read for particle
10 // system parameters named similarly to the variables below (see the
11 // particle archetype notecard for more details). if the object is
12 // rezzed with an existing particle system already read from a notecard
13 // that's still present, it will keep playing that particle system.
14 // if a notecard is added or changed, then the particle variables are
17 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
18 // do not use it in objects without fully realizing you are implicitly accepting that license.
21 // party culiar link message API.
23 integer PARTYCULIAR_HUFFWARE_ID = 10018;
24 // a unique ID within the huffware system for this script.
25 string HUFFWARE_PARM_SEPARATOR = "~~~";
26 // three tildes is an uncommon thing to have otherwise, so we use it to separate
27 // our commands in linked messages.
29 string PARTYCULIAR_POWER_COMMAND = "#powrpcl";
30 // tells the particle system to either turn on or off. the single parameter is
31 // either "on", "1", "true", or "off", "0", "false".
35 float MIN_ROTATION = 0.1; // rotations used for omega value to rotate particles.
36 float MAX_ROTATION = 4.0;
38 // items controlled by the notecard...
41 integer debug = FALSE; // controls whether logging occurs.
43 integer start_enabled = FALSE; // if true, particle system starts up right away.
46 integer interpolate_colors = TRUE;
47 integer randomize_colors = FALSE;
48 vector starting_color = <0.5, 0.7, 0.9>;
49 vector ending_color = <0.0, 1.0, 0.3>;
52 integer interpolate_size = TRUE;
53 vector final_dimensions = <1.8, 1.8, 1.8>;
54 vector initial_dimensions = <0.72, 0.72, 0.72>;
57 // 1.0 means completely opaque and 0.0 means completely transparent.
58 float initial_opacity = 1.0;
59 float final_opacity = 0.3;
61 // target following (or not)...
62 integer follow_target_key = FALSE;
63 key target_key = NULL_KEY;
65 // speed and acceleration of particles...
66 float minimum_velocity = 0.4;
67 float maximum_velocity = 1.4;
68 vector acceleration = <0.0, 0.4, 0.7>;
69 integer wind_blown = TRUE; // are particles affected by wind?
71 // freaky effects for particles...
72 integer bounce_off_z_height = FALSE; // start at height of containing object.
73 integer glowing = FALSE; // particles will glow.
74 integer source_centered = TRUE; // particles start at object's center.
75 integer point_toward_velocity = TRUE; // rotate vertical axis toward velocity.
77 string particle_texture = ""; // image used for the particle, if any.
79 // when randomizing colors, these control how long a choice lasts.
80 float minimum_hold_time = 5.0;
81 float maximum_hold_time = 20.0;
83 // timing (in seconds)...
84 float lifespan_for_particle = 3.0;
85 float lifespan_for_system = 0.0; // 0 means unlimited.
86 float creation_rate = 0.2; // delay between particle creations.
87 integer siblings = 7; // how many to create at once.
89 // how to exude particles.
90 integer emission_pattern
91 = PSYS_SRC_PATTERN_ANGLE; // 2D emission between given angles.
92 // = PSYS_SRC_PATTERN_DROP; // just plop them at center of object.
93 // = PSYS_SRC_PATTERN_EXPLODE; // spew them out as if exploded.
94 // = PSYS_SRC_PATTERN_ANGLE_CONE; // 3D emission between given angles.
95 // = PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY; // inverse of angle cone.
98 float starting_angle = 1.0; // initial angle where particles are emitted.
99 float ending_angle = 0.0; // bounding angle for emission.
100 vector rotation_between_bursts = <0.2, 0.2, 0.2>; // used in angle patterns.
101 float burst_radius = 2.0; // emission distance from source, unused for source centered.
103 // add-in... huffware script: notecard library, by fred huffhines
105 string current_notecard_name; // the name of the card we're reading now.
106 key current_query_id; // the query ID for the current notecard.
107 list query_contents; // the lines we have read from the notecard.
108 integer line_number; // which line are we at in notecard?
112 current_notecard_name = "";
113 current_query_id = NULL_KEY;
118 string dump_list(list to_show)
120 integer len = llGetListLength(to_show);
123 for (i = len - 1; i >= 0; i--) {
124 text += llList2String(to_show, i) + "\n";
131 create_particle_system()
133 if (randomize_colors) {
134 starting_color = <randomize_within_range(0.28, 0.95, FALSE),
135 randomize_within_range(0.28, 0.95, FALSE),
136 randomize_within_range(0.28, 0.95, FALSE)>;
137 ending_color = <randomize_within_range(0.14, 0.85, FALSE),
138 randomize_within_range(0.14, 0.85, FALSE),
139 randomize_within_range(0.14, 0.85, FALSE)>;
142 list system_parameters;
143 integer flags_for_particle_effects = 0;
145 if (interpolate_colors)
146 flags_for_particle_effects = PSYS_PART_INTERP_COLOR_MASK | flags_for_particle_effects;
147 system_parameters += [ PSYS_PART_START_COLOR, starting_color,
148 PSYS_PART_END_COLOR, ending_color ];
150 if (interpolate_size)
151 flags_for_particle_effects = PSYS_PART_INTERP_SCALE_MASK | flags_for_particle_effects;
152 system_parameters += [ PSYS_PART_START_SCALE, initial_dimensions,
153 PSYS_PART_END_SCALE, final_dimensions ];
155 system_parameters += [ PSYS_PART_START_ALPHA, initial_opacity,
156 PSYS_PART_END_ALPHA, final_opacity ];
158 if (follow_target_key && (target_key != NULL_KEY) ) {
159 flags_for_particle_effects = PSYS_PART_TARGET_POS_MASK | flags_for_particle_effects;
160 system_parameters += [ PSYS_SRC_TARGET_KEY, target_key ];
163 system_parameters += [ PSYS_SRC_BURST_SPEED_MIN, minimum_velocity,
164 PSYS_SRC_BURST_SPEED_MAX, maximum_velocity,
165 PSYS_SRC_ACCEL, acceleration ];
167 flags_for_particle_effects = PSYS_PART_WIND_MASK | flags_for_particle_effects;
169 if (particle_texture != "")
170 system_parameters += [ PSYS_SRC_TEXTURE, particle_texture ];
172 if (emission_pattern != 0)
173 system_parameters += [ PSYS_SRC_PATTERN, emission_pattern ];
175 system_parameters += [ PSYS_PART_MAX_AGE, lifespan_for_particle,
176 PSYS_SRC_MAX_AGE, lifespan_for_system,
177 PSYS_SRC_BURST_PART_COUNT, siblings,
178 PSYS_SRC_BURST_RATE, creation_rate ];
180 //hmmm: only add if used?
181 system_parameters += [ PSYS_SRC_ANGLE_BEGIN, starting_angle,
182 PSYS_SRC_ANGLE_END, ending_angle,
183 PSYS_SRC_OMEGA, rotation_between_bursts ];
185 // assorted effects...
186 if (bounce_off_z_height)
187 flags_for_particle_effects = PSYS_PART_BOUNCE_MASK | flags_for_particle_effects;
189 flags_for_particle_effects = PSYS_PART_EMISSIVE_MASK | flags_for_particle_effects;
190 if (point_toward_velocity)
191 flags_for_particle_effects = PSYS_PART_FOLLOW_VELOCITY_MASK | flags_for_particle_effects;
193 flags_for_particle_effects = PSYS_PART_FOLLOW_SRC_MASK | flags_for_particle_effects;
195 system_parameters += [ PSYS_SRC_BURST_RADIUS, burst_radius ]; // okay to use.
197 // now that we're done accumulating the flags, we can add them to our list.
198 system_parameters += [ PSYS_PART_FLAGS, flags_for_particle_effects ]; // must be last.
200 // and finally, we are ready to create the particle system of our dreams...
201 llParticleSystem(system_parameters);
204 // returns a non-empty string if "to_check" defines contents for "variable_name".
205 string defines_variable(string to_check, string variable_name)
207 // clean initial spaces.
208 while (llGetSubString(to_check, 0, 0) == " ")
209 to_check = llDeleteSubString(to_check, 0, 0);
210 if (!is_prefix(to_check, variable_name)) return "";
211 to_check = llDeleteSubString(to_check, 0, llStringLength(variable_name) - 1);
212 // clean any spaces or valid assignment characters.
213 while ( (llGetSubString(to_check, 0, 0) == " ")
214 || (llGetSubString(to_check, 0, 0) == "=")
215 || (llGetSubString(to_check, 0, 0) == ",") )
216 to_check = llDeleteSubString(to_check, 0, 0);
218 log_it("set " + variable_name + " = " + to_check);
219 // return what's left of the string.
223 parse_variable_definition(string to_parse)
225 string content; // filled after finding a variable name.
226 string texture_name; // temporary used in reading texture name.
228 if ( (content = defines_variable(to_parse, "debug")) != "")
229 debug = (integer)content;
230 else if ( (content = defines_variable(to_parse, "interpolate_colors")) != "")
231 interpolate_colors = (integer)content;
232 else if ( (content = defines_variable(to_parse, "randomize_colors")) != "")
233 randomize_colors = (integer)content;
234 else if ( (content = defines_variable(to_parse, "starting_color")) != "")
235 starting_color = (vector)content;
236 else if ( (content = defines_variable(to_parse, "ending_color")) != "")
237 ending_color = (vector)content;
238 else if ( (content = defines_variable(to_parse, "interpolate_size")) != "")
239 interpolate_size = (integer)content;
240 else if ( (content = defines_variable(to_parse, "initial_dimensions")) != "")
241 initial_dimensions = (vector)content;
242 else if ( (content = defines_variable(to_parse, "final_dimensions")) != "")
243 final_dimensions = (vector)content;
244 else if ( (content = defines_variable(to_parse, "initial_opacity")) != "")
245 initial_opacity = (float)content;
246 else if ( (content = defines_variable(to_parse, "final_opacity")) != "")
247 final_opacity = (float)content;
248 else if ( (content = defines_variable(to_parse, "follow_target_key")) != "")
249 follow_target_key = (integer)content;
250 else if ( (content = defines_variable(to_parse, "target_key")) != "")
251 target_key = (string)content;
252 else if ( (content = defines_variable(to_parse, "minimum_velocity")) != "")
253 minimum_velocity = (float)content;
254 else if ( (content = defines_variable(to_parse, "maximum_velocity")) != "")
255 maximum_velocity = (float)content;
256 else if ( (content = defines_variable(to_parse, "wind_blown")) != "")
257 wind_blown = (integer)content;
258 else if ( (content = defines_variable(to_parse, "acceleration")) != "")
259 acceleration = (vector)content;
260 else if ( (content = defines_variable(to_parse, "bounce_off_z_height")) != "")
261 bounce_off_z_height = (integer)content;
262 else if ( (content = defines_variable(to_parse, "glowing")) != "")
263 glowing = (integer)content;
264 else if ( (content = defines_variable(to_parse, "source_centered")) != "")
265 source_centered = (integer)content;
266 else if ( (content = defines_variable(to_parse, "point_toward_velocity")) != "")
267 point_toward_velocity = (integer)content;
268 else if ( (content = defines_variable(to_parse, "particle_texture")) != "")
269 particle_texture = (string)content;
270 else if ( (content = defines_variable(to_parse, "lifespan_for_particle")) != "")
271 lifespan_for_particle = (float)content;
272 else if ( (content = defines_variable(to_parse, "lifespan_for_system")) != "")
273 lifespan_for_system = (float)content;
274 else if ( (content = defines_variable(to_parse, "creation_rate")) != "")
275 creation_rate = (float)content;
276 else if ( (content = defines_variable(to_parse, "siblings")) != "")
277 siblings = (integer)content;
278 else if ( (content = defines_variable(to_parse, "minimum_hold_time")) != "")
279 minimum_hold_time = (float)content;
280 else if ( (content = defines_variable(to_parse, "maximum_hold_time")) != "")
281 maximum_hold_time = (float)content;
282 else if ( (content = defines_variable(to_parse, "emission_pattern")) != "") {
283 texture_name = (string)content;
284 // translate the short hand name into an emission_pattern value.
285 if (texture_name == "ANGLE") emission_pattern = PSYS_SRC_PATTERN_ANGLE;
286 else if (texture_name == "DROP") emission_pattern = PSYS_SRC_PATTERN_DROP;
287 else if (texture_name == "EXPLODE") emission_pattern = PSYS_SRC_PATTERN_EXPLODE;
288 else if (texture_name == "ANGLE_CONE") emission_pattern = PSYS_SRC_PATTERN_ANGLE_CONE;
289 else if (texture_name == "ANGLE_CONE_EMPTY")
290 emission_pattern = PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY;
291 //log_it("emission pattern is now " + (string)emission_pattern);
293 else if ( (content = defines_variable(to_parse, "starting_angle")) != "")
294 starting_angle = (float)content;
295 else if ( (content = defines_variable(to_parse, "ending_angle")) != "")
296 ending_angle = (float)content;
297 else if ( (content = defines_variable(to_parse, "rotation_between_bursts")) != "") {
298 if (content == "random")
299 rotation_between_bursts = <randomize_within_range(MIN_ROTATION, MAX_ROTATION, TRUE),
300 randomize_within_range(MIN_ROTATION, MAX_ROTATION, TRUE),
301 randomize_within_range(MIN_ROTATION, MAX_ROTATION, TRUE)>;
303 rotation_between_bursts = (vector)content;
304 } else if ( (content = defines_variable(to_parse, "burst_radius")) != "")
305 burst_radius = (float)content;
306 else if ( (content = defines_variable(to_parse, "running")) != "")
307 start_enabled = (integer)content;
309 // special cases for key to follow...
310 if (target_key == "owner") target_key = llGetOwner();
311 else if (target_key == "self") target_key = llGetKey();
312 // special cases for texture.
313 if (particle_texture == "inventory")
314 particle_texture = llGetInventoryName(INVENTORY_TEXTURE, 0);
318 process_particle_settings(list particle_definitions)
320 integer current_item = 0;
321 integer max_items = llGetListLength(particle_definitions);
322 while (current_item < max_items) {
323 string curr_line = llList2String(particle_definitions, current_item);
324 parse_variable_definition(curr_line);
331 if (randomize_colors) {
332 llSetTimerEvent(randomize_within_range(maximum_hold_time,
333 minimum_hold_time, FALSE));
337 stop_timer() { llSetTimerEvent(0.0); }
341 if (current_notecard_name != "") return;
342 // if there's a notecard, then we will start reading it.
343 if (llGetInventoryNumber(INVENTORY_NOTECARD) > 0) {
344 current_notecard_name = llGetInventoryName(INVENTORY_NOTECARD, 0);
347 current_query_id = llGetNotecardLine(current_notecard_name, 0);
352 // this should be invoked from the link_message event handler to process the requests
353 // for whatever service this library script provides.
354 handle_link_message(integer sender, integer huff_id, string msg, key id)
356 if (huff_id != PARTYCULIAR_HUFFWARE_ID) {
359 //llOwnerSay("link msg: " + (string)sender + " " + (string)huff_id + " msg=" + msg + " id=" + (string)id);
360 if (msg == PARTYCULIAR_POWER_COMMAND) {
362 if ( (find_substring(id, "on") == 0)
363 || (find_substring(id, "1") == 0)
364 || (find_substring(id, "true") == 0) ) {
365 // they want to crank the particle system up.
366 create_particle_system();
368 } else if ( (find_substring(id, "off") == 0)
369 || (find_substring(id, "0") == 0)
370 || (find_substring(id, "false") == 0) ) {
371 // the request is to shut the party down now.
372 llParticleSystem([]);
383 integer debug_num = 0;
385 // a debugging output method. can be disabled entirely in one place.
386 log_it(string to_say)
389 // tell this to the owner.
390 llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
391 // say this on open chat, but use an unusual channel.
392 // llSay(108, llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
397 // joins a list of parameters using the parameter sentinel for the library.
398 string wrap_parameters(list to_flatten)
399 { return llDumpList2String(to_flatten, HUFFWARE_PARM_SEPARATOR); }
401 // handles when blank strings need to come through the pipe.
402 string wrap_blank_string(string to_wrap)
404 if (llStringLength(to_wrap)) return to_wrap; // that one is okay.
405 return "\"\""; // return a quoted nothing as a signal for a blank.
408 // undoes a previously wrapped blank string.
409 string interpret_blank_string(string to_unwrap)
411 if (to_unwrap == "\"\"") return ""; // that was an encoded blank.
412 return to_unwrap; // no encoding.
415 // a simple version of a reply for a command that has been executed. the parameters
416 // might contain an outcome or result of the operation that was requested.
417 send_reply(integer destination, list parms, string command)
419 llMessageLinked(destination, PARTYCULIAR_HUFFWARE_ID, command,
420 llDumpList2String(parms, HUFFWARE_PARM_SEPARATOR));
425 // returns a number at most maximum and at least minimum.
426 // if "allow_negative" is TRUE, then the return may be positive or negative.
427 float randomize_within_range(float minimum, float maximum, integer allow_negative)
429 float to_return = minimum + llFrand(maximum - minimum);
430 if (allow_negative) {
431 if (llFrand(1.0) < 0.5) to_return *= -1.0;
436 // the string processing methods are not case sensitive.
438 // returns TRUE if the "pattern" is found in the "full_string".
439 integer matches_substring(string full_string, string pattern)
440 { return (find_substring(full_string, pattern) >= 0); }
442 // returns the index of the first occurrence of "pattern" inside
443 // the "full_string". if it is not found, then a negative number is returned.
444 integer find_substring(string full_string, string pattern)
445 { return llSubStringIndex(llToLower(full_string), llToLower(pattern)); }
447 // returns TRUE if the "prefix" string is the first part of "compare_with".
448 integer is_prefix(string compare_with, string prefix)
449 { return find_substring(compare_with, prefix) == 0; }
452 // huffware script: auto-retire, by fred huffhines, version 2.5.
453 // distributed under BSD-like license.
454 // !! keep in mind that this code must be *copied* into another
455 // !! script that you wish to add auto-retirement capability to.
456 // when a script has auto_retire in it, it can be dropped into an
457 // object and the most recent version of the script will destroy
458 // all older versions.
460 // the version numbers are embedded into the script names themselves.
461 // the notation for versions uses a letter 'v', followed by two numbers
462 // in the form "major.minor".
463 // major and minor versions are implicitly considered as a floating point
464 // number that increases with each newer version of the script. thus,
465 // "hazmap v0.1" might be the first script in the "hazmap" script continuum,
466 // and "hazmap v3.2" is a more recent version.
468 // example usage of the auto-retirement script:
471 // auto_retire(); // make sure newest addition is only version of script.
474 // this script is partly based on the self-upgrading scripts from markov brodsky
475 // and jippen faddoul.
478 string self = llGetScriptName(); // the name of this script.
479 list split = compute_basename_and_version(self);
480 if (llGetListLength(split) != 2) return; // nothing to do for this script.
481 string basename = llList2String(split, 0); // script name with no version attached.
482 string version_string = llList2String(split, 1); // the version found.
484 // find any scripts that match the basename. they are variants of this script.
485 for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
486 //log_it("invpo=" + (string)posn);
487 string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
488 if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
489 // found a basic match at least.
490 list inv_split = compute_basename_and_version(curr_script);
491 if (llGetListLength(inv_split) == 2) {
492 // see if this script is more ancient.
493 string inv_version_string = llList2String(inv_split, 1); // the version found.
494 // must make sure that the retiring script is completely the identical basename;
495 // just matching in the front doesn't make it a relative.
496 if ( (llList2String(inv_split, 0) == basename)
497 && ((float)inv_version_string < (float)version_string) ) {
498 // remove script with same name from inventory that has inferior version.
499 llRemoveInventory(curr_script);
506 // separates the base script name and version number. used by auto_retire.
507 list compute_basename_and_version(string to_chop_up)
509 // minimum script name is 2 characters plus a version.
510 integer space_v_posn;
511 // find the last useful space and 'v' combo.
512 for (space_v_posn = llStringLength(to_chop_up) - 3;
513 (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
515 // look for space and v but do nothing else.
516 //log_it("pos=" + (string)space_v_posn);
518 if (space_v_posn < 2) return []; // no space found.
519 //log_it("space v@" + (string)space_v_posn);
520 // now we zoom through the stuff after our beloved v character and find any evil
521 // space characters, which are most likely from SL having found a duplicate item
522 // name and not so helpfully renamed it for us.
524 for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
525 //log_it("indy=" + (string)space_v_posn);
526 if (llGetSubString(to_chop_up, indy, indy) == " ") {
527 // found one; zap it. since we're going backwards we don't need to
528 // adjust the loop at all.
529 to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
530 //log_it("saw case of previously redundant item, aieee. flattened: " + to_chop_up);
533 string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
534 // ditch the space character for our numerical check.
535 string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
536 // strip out a 'v' if there is one.
537 if (llGetSubString(chop_suffix, 0, 0) == "v")
538 chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
539 // if valid floating point number and greater than zero, that works for our version.
540 string basename = to_chop_up; // script name with no version attached.
541 if ((float)chop_suffix > 0.0) {
542 // this is a big success right here.
543 basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
544 return [ basename, chop_suffix ];
546 // seems like we found nothing useful.
553 state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
554 on_rez(integer parm) { state rerun; }
556 state rerun { state_entry() { state default; } }
565 create_particle_system();
568 llParticleSystem([]);
571 check_for_notecard();
575 if (current_query_id != NULL_KEY) {
576 // there's been a failure of some sort; we were supposed to get a notecard to read.
577 llWhisper(0, "Failed to read the notecard, now restarting.");
580 create_particle_system();
584 on_rez(integer parm) {
585 target_key = llGetKey(); // reset to get rid of weird wrong keys.
589 link_message(integer sender, integer num, string msg, key id) {
590 handle_link_message(sender, num, msg, id);
593 changed(integer change_type) {
594 if (change_type != CHANGED_INVENTORY) {
595 // we only care about inventory changes here.
601 dataserver(key query_id, string data) {
602 if (query_id != current_query_id) {
603 log_it("not our query id somehow? weird query had: id=" + (string)query_id + " data=" + (string)data);
604 // to heck with all this weirdness; if there's a failure, start over.
607 // if we're not at the end of the notecard we're reading...
610 if (data != "#party culiar") {
611 // this card has the wrong signature at the top. quit bothering
615 log_it("starting to read notecard " + current_notecard_name + "...");
618 // add the non-blank line to our destination list.
619 query_contents += data;
620 //log_it("line " + (string)line_number + ": data=" + data);
622 line_number++; // increase the line count.
623 // request the next line from the notecard.
624 current_query_id = llGetNotecardLine(current_notecard_name, line_number);
626 // no more data, so we're done with this card.
627 current_query_id = NULL_KEY;
628 if (!llGetListLength(query_contents)) {
629 // nothing was read? the heck with this card.
630 current_notecard_name = ""; // toss bad card.
633 //log_it("notecard said:\n" + dump_list(query_contents));
634 process_particle_settings(query_contents);
636 create_particle_system();
639 llParticleSystem([]);
642 log_it("done reading notecard " + current_notecard_name + ".");