2 // huffware script: non-script giver, by fred huffhines.
4 // gives all objects, notecards, etc contained in an object when that object is touched.
5 // does not give out scripts, since these are generally not something that should be handed
8 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
9 // do not use it in objects without fully realizing you are implicitly accepting that license.
12 integer ONLY_GIVE_TO_OWNER = TRUE;
13 // if this is true, then only the owner will receive a copy of the items.
15 integer USE_SCRIPT_NAME_FOR_FOLDER = FALSE;
16 // if this is set to true, then the objects will be given to the user in a folder named
17 // after *this* script, rather than the object in which the script lives.
19 integer GIVE_UNCOPYABLES = FALSE;
20 // this flag is dangerous when true, because it means that uncopyable objects will still
21 // be handed to the target. if that target refuses the non-copyable items, then the items
22 // will be lost forever. that is not so good if you've sold the person a non-copy item.
24 string EXCLUDED_NOTECARD = "product description";
25 // a special case; if there is a giftorse configuration card, we won't hand that out.
27 integer DEBUGGING = FALSE; // set to true for noisier runs.
29 key first_toucher; // tracks who clicked on the object to get contents.
31 // returns the proper folder name for the gifts to reside in, based on the choice made in
32 // USE_SCRIPT_NAME_FOR_FOLDER.
35 if (USE_SCRIPT_NAME_FOR_FOLDER) return llGetScriptName();
36 else return llGetObjectName();
39 // give out pictures, notecards, objects, etc. that are hiding in the object.
40 really_give_out_contents(key give_to)
42 list all_to_give = []; // the set we will hand over in a batch.
43 list uncopyables = []; // the list we have to do individually.
44 // find out how many items there are.
45 integer count = llGetInventoryNumber(INVENTORY_ALL);
46 // iterate across all the items and add them to the gift list if appropriate.
48 for (indy = 0; indy < count; indy++) {
49 string item_name = llGetInventoryName(INVENTORY_ALL, indy);
50 integer type = llGetInventoryType(item_name);
51 if ( (type != INVENTORY_SCRIPT)
52 && ( (type != INVENTORY_NOTECARD) || (item_name != EXCLUDED_NOTECARD) ) ) {
53 // it's okay to add this item; it's not a script and we are not skipping the special notecard.
54 integer mask = MASK_OWNER;
55 if (!ONLY_GIVE_TO_OWNER) mask = MASK_EVERYONE;
56 integer perms = llGetInventoryPermMask(item_name, mask);
57 if (perms & PERM_COPY) {
58 // a normal object that we can hand out.
59 all_to_give += item_name;
61 uncopyables += item_name;
65 // hand the customer the whole set as one big chunk, named after the object.
66 llGiveInventoryList(give_to, folder_name(), all_to_give);
68 // handle any problematic items. we cannot copy these objects into a category folder,
69 // so we can either not try to copy them (a lot safer) or we can try to deliver them
70 // normally as individual items. the latter choice is more dangerous, because if the
71 // owner discards these items rather than keeping them, the items will be lost forever!
72 if (llGetListLength(uncopyables) > 0) {
74 string is_verb = "is ";
75 string third_noun_subj = "it ";
76 string third_noun_obj = "it ";
77 if (llGetListLength(uncopyables) > 1) {
80 third_noun_subj = "they ";
81 third_noun_obj = "them ";
84 string uncopyable_message = "will be left inside the object. To get " + third_noun_obj
85 + ", please copy " + third_noun_obj + "\nmanually from this object into your inventory.";
86 if (GIVE_UNCOPYABLES) {
87 uncopyable_message = "will be moved over to your inventory."
88 + "\nPlease look in your main folders for "
89 + third_noun_obj + "(e.g., in Objects or Textures).";
92 string failure_message = "The item" + plural
93 + "[" + llDumpList2String(uncopyables, "; ") + "]\n"
94 + is_verb + "not copyable; " + third_noun_subj
97 if (llGetOwner() == give_to) {
98 // the object can be moved to inventory, but not with the category method.
99 llOwnerSay(failure_message);
101 // this seems like a weird case; it will probably just fail anyhow?
102 // if the item's not copyable and you're not the owner of this object,
103 // how can we give it to you?
104 llInstantMessage(give_to, failure_message);
107 // now that we've announced this weird situation, handle it appropriately.
108 if (GIVE_UNCOPYABLES) {
109 for (indy = 0; indy < llGetListLength(uncopyables); indy++) {
110 string item_name = llList2String(uncopyables, indy);
111 llGiveInventory(give_to, item_name);
113 } // otherwise leave them be.
119 // code borrowed from menutini to raise a menu asking if they actually meant to get all
120 // the contents. an opensim inventory bug makes all the folders look foolish if we
121 // do any inventory giving accidentally.
124 // global variables...
126 list _private_global_buttons; // holds onto the active set of menu options.
127 string _private_global_av_key; // the key for the avatar who clicks the menu.
128 string _private_global_title; // holds onto current title text.
130 integer _menu_base_start = 0; // position in the items of the current menu.
132 integer listening_id = 0;
133 // the current id of our listening for the menu. it's an id returned by LSL
134 // that we need to track so we can cancel the listen.
136 integer menu_system_channel = -123123;
137 // messages come back to us from this channel when user clicks the dialog.
138 // this is set later and the default is meaningless.
140 string global_menu_name = "";
141 // hangs onto the current menu's name.
143 //hmmm: note; to manage multiple concurrent menus on different channels,
144 // we must make these into lists. then the timeouts should apply
145 // individually to these instead of overall (if we even do timeouts;
146 // it's nicer if menus never stop being active).
148 string NEXT_MENU_TEXT = "Next >>";
149 // what the next item will say for showing next menu page.
151 //integer TIMEOUT_FOR_MENU = 42;
152 // timeout for the menu in seconds.
154 // displays the menu requested. it's "menu_name" is an internal name that is
155 // not displayed to the user. the "title" is the content shown in the main area
156 // of the menu. "commands_in" is the list of menu items to show as buttons.
157 // the "menu_channel" is where the user's clicked response will be sent. the
158 // "listen_to" key is the avatar expected to click the menu, which is needed to
159 // listen to his response.
160 show_menu(string menu_name, string title, list buttons,
161 integer menu_channel, key listen_to)
163 // save our new parms.
164 global_menu_name = menu_name;
165 _private_global_title = title;
166 _private_global_buttons = buttons;
167 menu_system_channel = menu_channel;
168 _private_global_av_key = listen_to;
170 log_it("menu name: " + global_menu_name);
171 log_it("title: " + _private_global_title);
172 log_it("buttons: " + (string)buttons);
173 log_it("channel: " + (string)menu_system_channel);
174 log_it("listen key: " + (string)listen_to);
177 integer add_next = FALSE; // true if we should add a next menu item.
179 // the math here incorporates current button position.
180 integer current = _menu_base_start;
181 integer max_buttons = llGetListLength(buttons) - current;
183 if (max_buttons > 12) {
184 // limitation of SL: menus have a max of 12 buttons.
187 } else if (llGetListLength(buttons) > 12) {
188 // we already have been adding next. let's make sure this gets
189 // a wrap-around next button.
192 // chop out what we can use in a menu.
193 list trunc_buttons = llList2List(buttons, current, current + max_buttons - 1);
195 // we were asked to add a menu item for the next screen.
196 trunc_buttons = llList2List(trunc_buttons, 0, 10) + NEXT_MENU_TEXT;
199 listening_id = llListen(menu_channel, "", listen_to, "");
202 // take only the prefix of the string, to avoid getting a length complaint.
203 for (i = 0; i < llGetListLength(trunc_buttons); i++) {
204 string curr = llList2String(trunc_buttons, i);
205 integer last_pos = 23; // default maximum, highest possible is 24.
206 if (llStringLength(curr) - 1 < last_pos) last_pos = llStringLength(curr) - 1;
207 curr = llGetSubString(curr, 0, last_pos);
210 llDialog(listen_to, title, commands, menu_channel);
213 // shuts down any connection we might have had with any active menu. we will not
214 // send any responses after this point (although we might already have responded when
215 // the user clicked the menu).
218 llListenRemove(listening_id);
221 // process the response when the user chooses a menu item.
222 process_menu_response(integer channel, string name, key id, string message)
224 if (channel != menu_system_channel) return; // not for us.
226 if (message == NEXT_MENU_TEXT) {
227 // this is the special choice, so we need to go to the next page.
228 _menu_base_start += 11;
229 if (_menu_base_start > llGetListLength(_private_global_buttons)) {
230 // we have wrapped around the list. go to the start again.
231 _menu_base_start = 0;
233 show_menu(global_menu_name, _private_global_title,
234 _private_global_buttons, menu_system_channel,
235 _private_global_av_key);
236 return; // handled by opening a new menu.
239 string calculated_name;
241 // first try for an exact match.
242 for (indy = 0; indy < llGetListLength(_private_global_buttons); indy++) {
243 string curr = llList2String(_private_global_buttons, indy);
244 if (curr == message) {
245 // correct the answer based on the full button string.
246 calculated_name = curr;
247 }//////////////[copy starting here...]//////////////
250 if (calculated_name == "") {
251 // try an imprecise match if the exact matching didn't work.
252 for (indy = 0; indy < llGetListLength(_private_global_buttons); indy++) {
253 string curr = llList2String(_private_global_buttons, indy);
254 if (is_prefix(curr, message)) {
255 // correct the answer based on the full button string.
256 calculated_name = curr;
260 if (calculated_name == "yes") {
261 // only send a response if that menu choice made sense to us.
262 really_give_out_contents(first_toucher);
267 // end from menutini.
273 // borrowed from hufflets...
275 integer debug_num = 0;
277 // a debugging output method. can be disabled entirely in one place.
278 log_it(string to_say)
281 // tell this to the owner.
282 llOwnerSay(llGetDate() + ": " + llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
283 //llWhisper(0, llGetDate() + ": " + llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
284 // say this on an unusual channel for chat if it's not intended for general public.
285 // llSay(108, llGetDate() + ": " + llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
286 // say this on open chat that anyone can hear. we take off the bling for this one.
290 // returns the index of the first occurrence of "pattern" inside
291 // the "full_string". if it is not found, then a negative number is returned.
292 integer find_substring(string full_string, string pattern)
293 { return llSubStringIndex(llToLower(full_string), llToLower(pattern)); }
295 // returns TRUE if the "prefix" string is the first part of "compare_with".
296 integer is_prefix(string compare_with, string prefix)
297 { return (llSubStringIndex(compare_with, prefix) == 0); }
299 // returns a number at most "maximum" and at least "minimum".
300 // if "allow_negative" is TRUE, then the return may be positive or negative.
301 float randomize_within_range(float minimum, float maximum, integer allow_negative)
303 if (minimum > maximum) {
304 // flip the two if they are reversed.
305 float temp = minimum; minimum = maximum; maximum = temp;
307 float to_return = minimum + llFrand(maximum - minimum);
308 if (allow_negative) {
309 if (llFrand(1.0) < 0.5) to_return *= -1.0;
315 // huffware script: auto-retire, by fred huffhines, version 2.8.
316 // distributed under BSD-like license.
317 // !! keep in mind that this code must be *copied* into another
318 // !! script that you wish to add auto-retirement capability to.
319 // when a script has auto_retire in it, it can be dropped into an
320 // object and the most recent version of the script will destroy
321 // all older versions.
323 // the version numbers are embedded into the script names themselves.
324 // the notation for versions uses a letter 'v', followed by two numbers
325 // in the form "major.minor".
326 // major and minor versions are implicitly considered as a floating point
327 // number that increases with each newer version of the script. thus,
328 // "hazmap v0.1" might be the first script in the "hazmap" script continuum,
329 // and "hazmap v3.2" is a more recent version.
331 // example usage of the auto-retirement script:
334 // auto_retire(); // make sure newest addition is only version of script.
337 // this script is partly based on the self-upgrading scripts from markov brodsky
338 // and jippen faddoul.
341 string self = llGetScriptName(); // the name of this script.
342 list split = compute_basename_and_version(self);
343 if (llGetListLength(split) != 2) return; // nothing to do for this script.
344 string basename = llList2String(split, 0); // script name with no version attached.
345 string version_string = llList2String(split, 1); // the version found.
347 // find any scripts that match the basename. they are variants of this script.
348 for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
349 string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
350 if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
351 // found a basic match at least.
352 list inv_split = compute_basename_and_version(curr_script);
353 if (llGetListLength(inv_split) == 2) {
354 // see if this script is more ancient.
355 string inv_version_string = llList2String(inv_split, 1); // the version found.
356 // must make sure that the retiring script is completely the identical basename;
357 // just matching in the front doesn't make it a relative.
358 if ( (llList2String(inv_split, 0) == basename)
359 && ((float)inv_version_string < (float)version_string) ) {
360 // remove script with same name from inventory that has inferior version.
361 llRemoveInventory(curr_script);
368 // separates the base script name and version number. used by auto_retire.
369 list compute_basename_and_version(string to_chop_up)
371 // minimum script name is 2 characters plus a version.
372 integer space_v_posn;
373 // find the last useful space and 'v' combo.
374 for (space_v_posn = llStringLength(to_chop_up) - 3;
375 (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
377 // look for space and v but do nothing else.
379 if (space_v_posn < 2) return []; // no space found.
380 // now we zoom through the stuff after our beloved v character and find any evil
381 // space characters, which are most likely from SL having found a duplicate item
382 // name and not so helpfully renamed it for us.
384 for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
385 if (llGetSubString(to_chop_up, indy, indy) == " ") {
386 // found one; zap it. since we're going backwards we don't need to
387 // adjust the loop at all.
388 to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
391 string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
392 // ditch the space character for our numerical check.
393 string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
394 // strip out a 'v' if there is one.
395 if (llGetSubString(chop_suffix, 0, 0) == "v")
396 chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
397 // if valid floating point number and greater than zero, that works for our version.
398 string basename = to_chop_up; // script name with no version attached.
399 if ((float)chop_suffix > 0.0) {
400 // this is a big success right here.
401 basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
402 return [ basename, chop_suffix ];
404 // seems like we found nothing useful.
412 state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
413 on_rez(integer parm) { state rerun; }
415 state rerun { state_entry() { state default; } }
421 // assign a random channel for the menu or we could be hearing others
423 menu_system_channel = -1 * (integer)randomize_within_range(256, 248248, FALSE);
426 on_rez(integer param) { llResetScript(); }
428 touch_start(integer num) {
429 first_toucher = llDetectedKey(0);
430 // are we only supposed to give stuff to the owner?
431 if (ONLY_GIVE_TO_OWNER && (first_toucher != llGetOwner()) ) {
432 first_toucher = NULL_KEY;
435 show_menu("askreally", "\nWould you like a copy of this object's contents? "
436 + "They will be put in a folder named " + folder_name() + ".",
437 ["yes", "no"], -18264, first_toucher);
440 listen(integer channel, string name, key id, string message)
441 { process_menu_response(channel, name, id, message); }