still finding fudge-up on startup
[feisty_meow.git] / huffware / huffotronic_scripts / welcomebot_v4.0.txt
1 
2 // huffware script: welcomebot visitor list, by fred huffhines
3 //
4 // originally based on LSL wiki examples.
5 // how to use this script:
6 //   1) create an object that you want to track visitors to your area.
7 //   2) change the parameters below to reflect your own information.
8 //   3) add the script to your tracker object.
9 //   4) reply to the script's rez email with the following commands:
10 //      list: send the current visitor list.
11 //      scan: scan the area near the greeter object.
12 //      clear: throw out the visitor list.  sends a copy before deleting.
13 //
14 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
15 // do not use it in objects without fully realizing you are implicitly accepting that license.
16 //
17
18 //todo:
19 // how do we reconcile having a config notecard with the giveaway ones?
20 //   just name it special?
21 // we want the script not to reset when the parameters change!
22 //    it should just reset the notecard stuff and other variables.
23 //    no new setup on email and all that stuff!
24 // maintain list of last visit times.
25 //    just as a string?
26
27
28 // put your e-mail address here.
29 string owner_email = "fred@gruntose.com";
30
31 // put the GUIDs of your business partners here.  the owner is automatically
32 // notified.
33 list notification_list = [];
34
35 // place name used to identify this visitor list.
36 string greeter_name = "eepaw shop welcomebot";
37
38 // the description of where this is.
39 string welcome_message = "Welcome to the eepaw shop (Eclectic Electric Patterns and Widgets).  Please touch the welcomebot for more information.";
40
41 // you can make the bot listen on a different channel if you like...
42 integer OWNER_CHANNEL = 14;
43
44 integer MAX_ITEMS_PER_LINE = 4;  // how many visitors to show on one line of output.
45
46 integer EMAIL_RECEPTION_ENABLED = FALSE;  // currently not enabled in opensim.
47
48 integer EMAIL_SENDING_ENABLED = FALSE;  // apparently many sims don't allow email sending.
49
50 integer MAX_VISITORS_TRACKED = 120;
51     // the number of items we keep around in the list of visitors.
52
53 float pause_when_mail_pending = 2.0;  // check for mail this frequently when there's a stack.
54 float pause_no_pending_mail = 42.0;  // if no mail had been waiting, we check more slackly.
55
56 // number of seconds between sending the owner a visitor list.
57 integer auto_list_interval = 43200;  // 43200 is every 12 hours.
58
59 // any variables that need to be modified are above this...
60 ///////////////////////////////////////////////////////////
61
62 float SENSOR_RANGE = 10.0; // in meters.
63
64 float SENSOR_RATE = 30.0; // in seconds.
65
66 // Global variables 
67 string regionName; 
68 key myKey; 
69 list visitor_list; 
70 list current_list; 
71 string scan_list; 
72 float maxZ = 0.0; 
73 float minZ = 0; 
74 integer MAXZ = 20; 
75 integer MINZ = 20; 
76 integer last_automatic_list_sent;  // when we last updated the owner automatically.
77
78 // sends an email.  somewhat a thin wrapper.
79 send_email(string recipient, string subject, string text)
80 {
81     if (EMAIL_SENDING_ENABLED) {
82         // some sims do not support email.
83         llEmail(recipient, subject, text);
84     }
85 }
86
87 cloaked_owner_alert(string text)
88 {
89     // temporarily change the name so emails will come from the expected greeter.
90     string hold_name = llGetObjectName();
91     llSetObjectName(greeter_name);
92     // send the message.
93     llOwnerSay(text);
94     // restore our name back to the original.
95     llSetObjectName(hold_name);
96 }
97
98 cloaked_IM(key who_to_tell, string text)
99 {
100     // temporarily change the name so emails will come from the expected greeter.
101     string hold_name = llGetObjectName();
102     llSetObjectName(greeter_name);
103     // send the message.
104     llInstantMessage(who_to_tell, text);
105     // restore our name back to the original.
106     llSetObjectName(hold_name);
107 }
108
109 // notifies all the people in the list who want to hear about visitors.
110 notify_folks(string text)
111 {
112     // temporarily change the name so emails will come from the expected greeter.
113     string hold_name = llGetObjectName();
114     llSetObjectName(greeter_name); 
115
116     list temp_notify = notification_list;
117     temp_notify += [llGetOwner()];
118         
119     integer i;
120     for (i = 0; i < llGetListLength(temp_notify); i++) {
121         string current_id = llList2String(temp_notify, i);
122         if ((key)current_id == llGetOwner()) cloaked_owner_alert(text);
123         else cloaked_IM((key)current_id, text);
124     }
125
126     // restore our name back to the original.
127     llSetObjectName(hold_name);
128 }
129
130 // looks for the name provided in a list.  the index is returned if
131 // the name is found, otherwise a negative number is returned.
132 integer find_name_in_list(string name, list avlist) 
133
134     integer len = llGetListLength(avlist);
135     integer i; 
136     for (i = 0; i < len; i++) 
137     { 
138         if (llList2String(avlist, i) == name) 
139             return i; 
140     } 
141     return -1;
142
143
144 // looks for changes in the current locale.
145 check_region(list detected) 
146 {
147     // we accumulate information about people who have changed state in the variables below.
148     string names_entering = ""; 
149     string names_leaving = ""; 
150     integer num_entering = 0; 
151     integer num_leaving = 0; 
152
153     // see if we have somebody new.
154     integer len = llGetListLength(detected); 
155     integer i; 
156     for (i = 0; i < len; i++) { 
157         string name = llList2String(detected, i); 
158         if (find_name_in_list(name, current_list) < 0) { 
159             current_list += name; 
160             if (num_entering > 0) names_entering += ", "; 
161             names_entering += name; 
162             num_entering++; 
163         } 
164     } 
165      
166     // see if somebody left.
167     len = llGetListLength(current_list); 
168     for (i = 0; i < len; i++) { 
169         string name = llList2String(current_list, i); 
170         if (find_name_in_list(name, detected) < 0) {
171             if (num_leaving > 0) names_leaving += ", "; 
172             names_leaving += name; 
173             num_leaving++; 
174         } 
175     } 
176
177     // update current list to be current only     
178     current_list = detected;
179
180     // create a message describing the state.
181     string msg = ""; 
182     if (num_entering > 0) msg = "Arrived: " + names_entering;
183     if (num_leaving > 0) {
184         if (llStringLength(msg) > 0) msg += "\n";
185         msg += "Left: " + names_leaving;
186     }
187     if (msg != "") notify_folks(msg); 
188
189
190 string build_visitor_list()
191 {
192     string text = greeter_name + ":\n"; 
193     integer len = llGetListLength(visitor_list); 
194     integer i;
195     integer items_per_line = 0;
196     for (i = len - 1; i >= 0; i--) {
197         if (items_per_line != 0) {
198             text += "\t";
199         }
200         text += llList2String(visitor_list, i);
201         items_per_line++;
202         if (items_per_line >= MAX_ITEMS_PER_LINE) {
203             text += "\n";
204             items_per_line = 0;
205         }
206     }
207     // handle case for last line.
208     if (items_per_line != 0) text += "\n";
209     text += "Total=" + (string)len; 
210     return text;
211 }
212
213 send_list_in_text()
214 {
215     llOwnerSay(build_visitor_list());
216 }
217
218 send_scan_in_text()
219 {
220     llOwnerSay(greeter_name + " scan\n" + scan_list);
221 }
222
223 send_list_in_email() 
224
225     string text = build_visitor_list();
226     send_email(owner_email, greeter_name + " visitor list", text); 
227
228
229 send_scan_in_email() 
230
231     send_email(owner_email, greeter_name + " scan", scan_list); 
232
233
234 clear_list()
235 {
236     visitor_list = [];
237 }
238
239 initialize_greeter() 
240
241     last_automatic_list_sent = llGetUnixTime(); 
242      
243     scan_list = ""; 
244      
245     myKey = llGetKey(); 
246     regionName = llGetRegionName(); 
247      
248     vector rezPos = llGetPos(); 
249     maxZ = rezPos.z + MAXZ; 
250     minZ = rezPos.z - MINZ; 
251     string rezInfo = "Key: "+(string)myKey + " @ "+regionName+" "+(string)rezPos+" ("+(string)maxZ+")"; 
252     notify_folks(rezInfo);
253     send_email(owner_email,  greeter_name + " Rez Information", rezInfo); 
254     llSetTimerEvent(pause_no_pending_mail); 
255     llSensorRepeat( "", "", AGENT, SENSOR_RANGE, TWO_PI, SENSOR_RANGE);
256     // hook up the bot to the radio so it will hear commands.
257     llListen(OWNER_CHANNEL, "", llGetOwner(), "");
258     llOwnerSay("Listening to commands on channel " + (string)OWNER_CHANNEL
259         + " (list, scan, clear)");
260 }
261
262 // performs the requested command.
263 process_command_locally(string msg)
264 {
265     if (msg == "list") send_list_in_text(); 
266     else if (msg == "scan") send_scan_in_text();
267     else if (msg == "clear") {
268         send_list_in_text();
269         clear_list();
270     }    
271 }
272
273 //////////////
274 // huffware script: auto-retire, by fred huffhines, version 2.5.
275 // distributed under BSD-like license.
276 //   !!  keep in mind that this code must be *copied* into another
277 //   !!  script that you wish to add auto-retirement capability to.
278 // when a script has auto_retire in it, it can be dropped into an
279 // object and the most recent version of the script will destroy
280 // all older versions.
281 //
282 // the version numbers are embedded into the script names themselves.
283 // the notation for versions uses a letter 'v', followed by two numbers
284 // in the form "major.minor".
285 // major and minor versions are implicitly considered as a floating point
286 // number that increases with each newer version of the script.  thus,
287 // "hazmap v0.1" might be the first script in the "hazmap" script continuum,
288 // and "hazmap v3.2" is a more recent version.
289 //
290 // example usage of the auto-retirement script:
291 //     default {
292 //         state_entry() {
293 //            auto_retire();  // make sure newest addition is only version of script.
294 //        }
295 //     }
296 // this script is partly based on the self-upgrading scripts from markov brodsky
297 // and jippen faddoul.
298 //////////////
299 auto_retire() {
300     string self = llGetScriptName();  // the name of this script.
301     list split = compute_basename_and_version(self);
302     if (llGetListLength(split) != 2) return;  // nothing to do for this script.
303     string basename = llList2String(split, 0);  // script name with no version attached.
304     string version_string = llList2String(split, 1);  // the version found.
305     integer posn;
306     // find any scripts that match the basename.  they are variants of this script.
307     for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
308 //log_it("invpo=" + (string)posn);
309         string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
310         if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
311             // found a basic match at least.
312             list inv_split = compute_basename_and_version(curr_script);
313             if (llGetListLength(inv_split) == 2) {
314                 // see if this script is more ancient.
315                 string inv_version_string = llList2String(inv_split, 1);  // the version found.
316                 // must make sure that the retiring script is completely the identical basename;
317                 // just matching in the front doesn't make it a relative.
318                 if ( (llList2String(inv_split, 0) == basename)
319                     && ((float)inv_version_string < (float)version_string) ) {
320                     // remove script with same name from inventory that has inferior version.
321                     llRemoveInventory(curr_script);
322                 }
323             }
324         }
325     }
326 }
327 //
328 // separates the base script name and version number.  used by auto_retire.
329 list compute_basename_and_version(string to_chop_up)
330 {
331     // minimum script name is 2 characters plus a version.
332     integer space_v_posn;
333     // find the last useful space and 'v' combo.
334     for (space_v_posn = llStringLength(to_chop_up) - 3;
335         (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
336         space_v_posn--) {
337         // look for space and v but do nothing else.
338 //log_it("pos=" + (string)space_v_posn);
339     }
340     if (space_v_posn < 2) return [];  // no space found.
341 //log_it("space v@" + (string)space_v_posn);
342     // now we zoom through the stuff after our beloved v character and find any evil
343     // space characters, which are most likely from SL having found a duplicate item
344     // name and not so helpfully renamed it for us.
345     integer indy;
346     for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
347 //log_it("indy=" + (string)space_v_posn);
348         if (llGetSubString(to_chop_up, indy, indy) == " ") {
349             // found one; zap it.  since we're going backwards we don't need to
350             // adjust the loop at all.
351             to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
352 //log_it("saw case of previously redundant item, aieee.  flattened: " + to_chop_up);
353         }
354     }
355     string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
356     // ditch the space character for our numerical check.
357     string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
358     // strip out a 'v' if there is one.
359     if (llGetSubString(chop_suffix, 0, 0) == "v")
360         chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
361     // if valid floating point number and greater than zero, that works for our version.
362     string basename = to_chop_up;  // script name with no version attached.
363     if ((float)chop_suffix > 0.0) {
364         // this is a big success right here.
365         basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
366         return [ basename, chop_suffix ];
367     }
368     // seems like we found nothing useful.
369     return [];
370 }
371 //
372 //////////////
373
374 default
375 {
376     state_entry() { if (llSubStringIndex(llGetObjectName(),  "huffotronic") < 0) state real_default; }
377     on_rez(integer parm) { state rerun; }
378 }
379 state rerun { state_entry() { state default; } }
380
381 state real_default 
382
383     state_entry() {
384         auto_retire();
385         initialize_greeter(); 
386     }
387      
388     on_rez(integer param) { llResetScript(); } 
389      
390     timer() 
391     {
392         if (EMAIL_RECEPTION_ENABLED) {
393             llGetNextEmail("", ""); // check for email with any subject/sender 
394              
395             integer curTime = llGetUnixTime(); 
396             if (curTime - last_automatic_list_sent < auto_list_interval) return; 
397             last_automatic_list_sent = curTime; 
398             send_list_in_email(); 
399         }
400     } 
401
402     email(string time, string address, string subj, string message, integer num_left) 
403     { 
404         list args = llParseString2List(message, [" "], []); 
405         if (llGetListLength(args) < 1) return;  // nothing to do.
406         string cmd = llToLower(llList2String(args, 0)); 
407 //room for a process_command_for_email here.
408         if (cmd == "list") send_list_in_email(); 
409         else if (cmd == "scan") send_scan_in_email();
410         else if (cmd == "clear") {
411             send_list_in_email();
412             clear_list();
413         }
414
415         if (num_left == 0) llSetTimerEvent(pause_no_pending_mail);  // sleep longer before checking.
416         else llSetTimerEvent(pause_when_mail_pending);  // short sleep since others are waiting.
417     }
418     
419     listen(integer channel, string name, key id, string msg)
420     {
421         if ( (channel != OWNER_CHANNEL) || (id != llGetOwner()) ) return;  // not for them.
422         process_command_locally(msg);
423     }
424
425     // when a visitor touches the welcomebot, hand out the first landmark and
426     // first notecard we have stored.
427     touch_start(integer count) {
428         string item_name = llGetInventoryName(INVENTORY_NOTECARD, 0);  // first notecard.
429         if (item_name != "")
430             llGiveInventory(llDetectedKey(0), item_name);
431         item_name = llGetInventoryName(INVENTORY_LANDMARK, 0);
432         if (item_name != "")
433             llGiveInventory(llDetectedKey(0), item_name);
434     }
435
436     sensor(integer number_detected) 
437     { 
438         list detected = []; 
439         vector pos = llGetPos(); 
440         string textVerbose = ""; 
441         string textNewVerbose = ""; 
442         string textNew = ""; 
443         integer other = 0; 
444         integer group = 0; 
445         integer new = 0; 
446         integer outer; 
447          
448         textVerbose = ""; 
449         for (outer = 0; outer < number_detected; outer++) 
450         { 
451             vector dpos = llDetectedPos(outer); 
452             if (llGetLandOwnerAt(dpos)==llGetLandOwnerAt(llGetPos())) 
453             { 
454                 string detected_name = llDetectedName(outer); 
455                 string detected_key = llDetectedKey(outer); 
456                 integer info_res = llGetAgentInfo(detected_key); 
457                 float diff = llVecDist(pos,dpos); 
458                 integer dist = llRound(diff); 
459                 string result = detected_name+" ("+detected_key+") " + (string)dist; 
460                 if(dpos.y>pos.y) result+="N"; 
461                 else if(dpos.y<pos.y) result+="S"; 
462                 if(dpos.x>pos.x) result+="E"; 
463                 else if(dpos.x<pos.x) result+="W"; 
464                 result += (string)((integer)(dpos.z-pos.z)); 
465                 result += " "; 
466                 if(info_res & AGENT_SCRIPTED)   result += "S"; 
467                 if(info_res & AGENT_FLYING)     result += "F"; 
468                 if(info_res & AGENT_AWAY)       result += "A"; 
469                 if(info_res & AGENT_IN_AIR)     result += "H"; 
470                 if(info_res & AGENT_MOUSELOOK)  result += "M"; 
471                 if (llDetectedGroup(outer)) result += "G"; 
472              
473                 if ((dpos.x >= 0 && dpos.x <= 256) && 
474                     (dpos.y >= 0 && dpos.y <= 256) && 
475                     (dpos.z >= minZ) && (dpos.z <= maxZ)) 
476                 { 
477                     if (llDetectedGroup(outer)) group++; 
478                     else other++; 
479
480                     // add to the detected list 
481                     detected += detected_name; 
482                      
483                     // see if they are on the visitor list 
484                     if (find_name_in_list(detected_name, visitor_list) < 0) {
485                         // make sure we haven't gone beyond our limit.
486                         if (llGetListLength(visitor_list) >= MAX_VISITORS_TRACKED) {
487                             visitor_list = llDeleteSubList(visitor_list, 0, 0);
488                         }
489                         // add to the visitor list 
490                         visitor_list += detected_name;
491                          
492                         // handle any separators to make more readable 
493                         if (new > 0)  { 
494                             textNew += ", "; 
495                             textNewVerbose += "\n"; 
496                         } 
497                          
498                         // add to the text 
499                         textNew += detected_name; 
500                         textNewVerbose += detected_name+" ("+detected_key+") @ "+regionName+" "+(string)dpos; 
501                         result += "N"; 
502                          
503                         // increment the count of new visitors 
504                         new++; 
505                         cloaked_IM(detected_key, welcome_message);
506                     } 
507                 } 
508                 else 
509                 { 
510                     result += "*"; 
511                 } 
512                 textVerbose += result + "\n"; 
513             } 
514         } 
515          
516         if (new > 0) {
517             // extra verbose logging.
518 //            notify_folks(textNew + "\n" + textNewVerbose);
519         }
520         scan_list = textVerbose; 
521          
522         check_region(detected); 
523     } 
524      
525     no_sensor() 
526     { 
527         check_region([]);
528     } 
529 }