6759f73e2a4dc172d21b5a915c9daeb5f60759ad
[feisty_meow.git] / huffware / huffotronic_scripts / non-script_giver_v3.2.txt
1 
2 // huffware script: non-script giver, by fred huffhines.
3 //
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
6 // to the customer.
7 //
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.
10 //
11
12 integer ONLY_GIVE_TO_OWNER = TRUE;
13     // if this is true, then only the owner will receive a copy of the items.
14
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.
18
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.
23
24 string EXCLUDED_NOTECARD = "product description";
25     // a special case; if there is a giftorse configuration card, we won't hand that out.
26
27 integer DEBUGGING = FALSE;  // set to true for noisier runs.
28
29 key first_toucher;  // tracks who clicked on the object to get contents.
30
31 // returns the proper folder name for the gifts to reside in, based on the choice made in
32 // USE_SCRIPT_NAME_FOR_FOLDER.
33 string folder_name()
34 {
35     if (USE_SCRIPT_NAME_FOR_FOLDER) return llGetScriptName();
36     else return llGetObjectName();
37 }
38
39 // give out pictures, notecards, objects, etc. that are hiding in the object.
40 really_give_out_contents(key give_to)
41 {
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.
47     integer indy;
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;
60             } else {
61                 uncopyables += item_name;
62             }
63         }
64     }
65     // hand the customer the whole set as one big chunk, named after the object.
66     llGiveInventoryList(give_to, folder_name(), all_to_give);
67
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) {
73         string plural = " ";
74         string is_verb = "is ";
75         string third_noun_subj = "it ";
76         string third_noun_obj = "it ";
77         if (llGetListLength(uncopyables) > 1) {
78             plural = "s ";
79             is_verb = "are ";
80             third_noun_subj = "they ";
81             third_noun_obj = "them ";
82         }
83         
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).";
90         }
91         
92         string failure_message = "The item" + plural
93             + "[" + llDumpList2String(uncopyables, "; ") + "]\n"
94             + is_verb + "not copyable; " + third_noun_subj
95             + uncopyable_message;
96             
97         if (llGetOwner() == give_to) {
98             // the object can be moved to inventory, but not with the category method.
99             llOwnerSay(failure_message);
100         } else {
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);
105         }
106         
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);
112             }
113         }  // otherwise leave them be.
114     }
115 }
116
117
118 //////////////
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.
122 //////////////
123
124 // global variables...
125
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.
129
130 integer _menu_base_start = 0;  // position in the items of the current menu.
131
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.
135
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.
139
140 string global_menu_name = "";
141     // hangs onto the current menu's name.
142
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).
147
148 string NEXT_MENU_TEXT = "Next >>";
149     // what the next item will say for showing next menu page.
150     
151 //integer TIMEOUT_FOR_MENU = 42;
152     // timeout for the menu in seconds.
153
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)
162 {
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;
169     if (DEBUGGING) {
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);
175     }
176
177     integer add_next = FALSE;  // true if we should add a next menu item.
178
179     // the math here incorporates current button position.
180     integer current = _menu_base_start;
181     integer max_buttons = llGetListLength(buttons) - current;
182
183     if (max_buttons > 12) {
184         // limitation of SL: menus have a max of 12 buttons.
185         max_buttons = 12;
186         add_next = TRUE;
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.
190         add_next = TRUE;
191     }
192     // chop out what we can use in a menu.
193     list trunc_buttons = llList2List(buttons, current, current + max_buttons - 1);
194     if (add_next) {
195         // we were asked to add a menu item for the next screen.
196         trunc_buttons = llList2List(trunc_buttons, 0, 10) + NEXT_MENU_TEXT;
197     }
198
199     listening_id = llListen(menu_channel, "", listen_to, "");
200     list commands;
201     integer i;
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);
208         commands += curr;
209     }
210     llDialog(listen_to, title, commands, menu_channel);
211 }
212
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).
216 clear_menu()
217 {
218     llListenRemove(listening_id);
219 }
220
221 // process the response when the user chooses a menu item.
222 process_menu_response(integer channel, string name, key id, string message)
223 {
224   if (channel != menu_system_channel) return;  // not for us.
225   
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;
232         }
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.
237     }
238     
239     string calculated_name;
240     integer indy;
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...]//////////////
248
249     }
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;
257             }
258         }
259     }
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);
263         clear_menu();
264     }
265 }
266
267 // end from menutini.
268 //////////////
269
270
271 //////////////
272 //
273 // borrowed from hufflets...
274
275 integer debug_num = 0;
276
277 // a debugging output method.  can be disabled entirely in one place.
278 log_it(string to_say)
279 {
280     debug_num++;
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.
287 //    llSay(0, to_say);
288 }
289
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)); }
294
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); }
298
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)
302 {
303     if (minimum > maximum) {
304         // flip the two if they are reversed.
305         float temp = minimum; minimum = maximum; maximum = temp;
306     }
307     float to_return = minimum + llFrand(maximum - minimum);
308     if (allow_negative) {
309         if (llFrand(1.0) < 0.5) to_return *= -1.0;
310     }
311     return to_return;
312 }
313
314 //////////////
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.
322 //
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.
330 //
331 // example usage of the auto-retirement script:
332 //     default {
333 //         state_entry() {
334 //            auto_retire();  // make sure newest addition is only version of script.
335 //        }
336 //     }
337 // this script is partly based on the self-upgrading scripts from markov brodsky
338 // and jippen faddoul.
339 //////////////
340 auto_retire() {
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.
346     integer posn;
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);
362                 }
363             }
364         }
365     }
366 }
367 //
368 // separates the base script name and version number.  used by auto_retire.
369 list compute_basename_and_version(string to_chop_up)
370 {
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");
376         space_v_posn--) {
377         // look for space and v but do nothing else.
378     }
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.
383     integer indy;
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);
389         }
390     }
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 ];
403     }
404     // seems like we found nothing useful.
405     return [];
406 }
407 //
408 //////////////
409
410 default
411 {
412     state_entry() { if (llSubStringIndex(llGetObjectName(),  "huffotronic") < 0) state real_default; }
413     on_rez(integer parm) { state rerun; }
414 }
415 state rerun { state_entry() { state default; } }
416
417 state real_default
418 {
419     state_entry() { 
420         auto_retire();
421         // assign a random channel for the menu or we could be hearing others
422         // menu responses.
423         menu_system_channel = -1 * (integer)randomize_within_range(256, 248248, FALSE);
424     }
425     
426     on_rez(integer param) { llResetScript(); }
427
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;
433             return;  // bail out.
434         }
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);
438     }
439
440     listen(integer channel, string name, key id, string message)
441     { process_menu_response(channel, name, id, message); }
442
443 }