Visitors

From Second Life Wiki
Jump to: navigation, search
KBnote.png Note: Due to licensing issues, all contributions to this wiki have stopped and the articles that we posted are just being maintained. This is one of the projects that has gone further and for updates you are cordially invited to the project page on our wiki.

Created by Kira Komarov and designed for Heather Iwish.

  • Updated to also include display names from Foxxe Wilder
  • Updated to include an access list so that control can be shared, for Keaton Oconnell
  • Updated to include re-logging visitors (visitors that come back after a configurable amount of time), for Keaton Oconnell

The following is a simple menu-driven visitor tracking script which scans for avatars in certain intervals and at a specified distance. Simply drop this script in a prim and touch it to configure it to your liking. The "View" button will return a message to the owner of the script with a list of visitors and at what time they were seen by the script.

The script does not support state persistency. That is, once the SIM restarts the data will be lost since the script will be reset. However, it is possible to couple this script with the memory module if you wish the script to survive SIM restarts and keep the list of visitors intact.

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
list vList;
 
integer comHandle;
integer comChannel;
 
integer scanInterval = 2;
integer scanRange = 10;
 
default {
 
    state_entry() {
        llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
    }
 
 
    touch_start(integer total_number) {
        if ((llDetectedKey(0) != llGetOwner())) return;
        (comChannel = ((((integer)("0x" + llGetSubString(((string)llGetKey()),-8,-1))) & 1073741823) ^ -1073741825));
        (comHandle = llListen(comChannel,"",llGetOwner(),""));
        llDialog(llDetectedKey(0),"\n[K] Visitor list: Please select an option. \n",["Reset","View","Range","Interval"],comChannel);
    }
 
 
    listen(integer chan,string name,key id,string mes) {
        if ((mes == "View")) {
            jump ztell;
        }
        if ((mes == "Reset")) {
            (vList = []);
            jump zout;
        }
        if ((mes == "Interval")) {
            llDialog(llGetOwner(),"\n[K] Visitor list: Please select a scanning interval in seconds.\n",["2s","4s","8s", "16s", "32s", "64s"],comChannel);
            jump zin;
        }
        if((integer)mes && !((integer)mes & ((integer)mes-1))) {
            (scanInterval = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        if ((mes == "Range")) {
            llDialog(llGetOwner(),"\n[K] Visitor: Please select a scanning distance in meters.\n",["5","10","15","30","35","40","45","50","60","70","80","90"],comChannel);
            jump zin;
        }
        if ((integer)mes%5==0) {
            (scanRange = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        jump zin;
        @ztell;
        integer aPtr;
        llOwnerSay("----------------------- Visitors -------------------------");
        for ((aPtr = llGetListLength(vList)-1); (aPtr > -1); (aPtr-=2)) {
            llOwnerSay(llList2String(vList,aPtr) + " @ " + llList2String(vList,aPtr-1));
            llOwnerSay("------------------------------------------------------------");
        }
        @zout;
        llListenRemove(comHandle);
        @zin;
    }
 
 
    sensor(integer num_detected) {
        integer aPtr;
        for ((aPtr = (num_detected - 1)); (aPtr > -1); (--aPtr)) {
            if ((!(~llListFindList(vList,((list)llDetectedName(aPtr)))))) {
                (vList += ([llGetTimestamp()] + [llDetectedName(aPtr)]));
            }
        }
    }
}

By indications from Foxxe Wilder, it seems to be the case that one would want to link display names to usernames in a visitor script such as this. Foxxe Wilder is an estate manager and sometimes renters report other players by their display names and there is no way to link the username to the display name in case they have to be chased up.

The following script is another version which also logs display names based on that concept:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
list vList;
 
integer comHandle;
integer comChannel;
 
integer scanInterval = 2;
integer scanRange = 10;
 
default {
 
    state_entry() {
        llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
    }
 
 
    touch_start(integer total_number) {
        if ((llDetectedKey(0) != llGetOwner())) return;
        (comChannel = ((((integer)("0x" + llGetSubString(((string)llGetKey()),-8,-1))) & 1073741823) ^ -1073741825));
        (comHandle = llListen(comChannel,"",llGetOwner(),""));
        llDialog(llDetectedKey(0),"\n[K] Visitor list: Please select an option. \n",["Reset","View","Range","Interval"],comChannel);
    }
 
 
    listen(integer chan,string name,key id,string mes) {
        if ((mes == "View")) {
            jump ztell;
        }
        if ((mes == "Reset")) {
            (vList = []);
            jump zout;
        }
        if ((mes == "Interval")) {
            llDialog(llGetOwner(),"\n[K] Visitor list: Please select a scanning interval in seconds.\n",["2s","4s","8s", "16s", "32s", "64s"],comChannel);
            jump zin;
        }
        if((integer)mes && !((integer)mes & ((integer)mes-1))) {
            (scanInterval = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        if ((mes == "Range")) {
            llDialog(llGetOwner(),"\n[K] Visitor: Please select a scanning distance in meters.\n",["5","10","15","30","35","40","45","50","60","70","80","90"],comChannel);
            jump zin;
        }
        if ((integer)mes%5==0) {
            (scanRange = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        jump zin;
        @ztell;
        integer aPtr;
        llOwnerSay("----------------------- Visitors -------------------------");
        for ((aPtr = llGetListLength(vList)-1); (aPtr > -1); (aPtr-=3)) {
            llOwnerSay(llList2String(vList,aPtr) + " / " + llList2String(vList,aPtr-1) + " @ " + llList2String(vList,aPtr-2));
            llOwnerSay("------------------------------------------------------------");
        }
        @zout;
        llListenRemove(comHandle);
        @zin;
    }
 
 
    sensor(integer num_detected) {
        integer aPtr;
        for ((aPtr = (num_detected - 1)); (aPtr > -1); (--aPtr)) {
            if ((!(~llListFindList(vList,((list)llDetectedName(aPtr)))))) {
                (vList += ([llGetTimestamp()] + [llDetectedName(aPtr)] + [llGetDisplayName(llDetectedKey(aPtr))]));
            }
        }
    }
}

Yet another variation is another version I created for Keaton Oconnell so that the script is based on an access list allowing multiple users in the access list to manipulate the object and deny access to others. For this to work, simply drop the script in the prim you have chosen and also drop a notecard called "Access List" in the same prim. In the notecard "Access List" in the prim, supply all the names (usernames) allowed to manipulate the object line by line. For example, if one would wish Keaton Oconnell to have sole access to the visitor prim, then the notecard should contain only one line: "Keaton Oconnell". If one would wish to allow Kira Komarov and Keaton Oconnell, then the notecard would contain:

Kira Komarov
Keaton Oconnell

Script follows:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
list vList;
list aList;
 
key aQuery;
 
integer comHandle = 0;
integer comChannel;
 
integer scanInterval = 2;
integer scanRange = 10;
 
default {
 
    state_entry() {
        (aQuery = llGetNotecardLine("Access List",comHandle));
        llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
    }
 
    changed(integer change) {
        if ((change & 1)) {
            (aList = []);
            (comHandle = 0);
            (aQuery = llGetNotecardLine("Access List",comHandle));
        }
    }
 
    touch_start(integer total_number) {
        if ((!(~llListFindList(aList,((list)llDetectedName(0)))))) return;
        (comChannel = ((((integer)("0x" + llGetSubString(((string)llGetKey()),-8,-1))) & 1073741823) ^ -1073741825));
        (comHandle = llListen(comChannel,"",llDetectedKey(0),""));
        llDialog(llDetectedKey(0),"\n[K] Visitor list: Please select an option. \n",["Reset","View","Range","Interval"],comChannel);
    }
 
    listen(integer chan,string name,key id,string mes) {
        if ((mes == "View")) {
            jump ztell;
        }
        if ((mes == "Reset")) {
            (vList = []);
            jump zout;
        }
        if ((mes == "Interval")) {
            llDialog(id,"\n[K] Visitor list: Please select a scanning interval in seconds.\n",["2s","4s","8s","16s","32s","64s"],comChannel);
            jump zin;
        }
        if ((((integer)mes) && (!(((integer)mes) & (((integer)mes) - 1))))) {
            (scanInterval = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        if ((mes == "Range")) {
            llDialog(id,"\n[K] Visitor: Please select a scanning distance in meters.\n",["5","10","15","30","35","40","45","50","60","70","80","90"],comChannel);
            jump zin;
        }
        if (((((integer)mes) % 5) == 0)) {
            (scanRange = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        jump zin;
        @ztell;
        integer aPtr;
        llInstantMessage(id,"----------------------- Visitors -------------------------");
        for ((aPtr = (llGetListLength(vList) - 1)); (aPtr > -1); (aPtr -= 2)) {
            llInstantMessage(id,((llList2String(vList,aPtr) + " @ ") + llList2String(vList,(aPtr - 1))));
            llInstantMessage(id,"------------------------------------------------------------");
        }
        @zout;
        llListenRemove(comHandle);
        @zin;
    }
 
    sensor(integer num_detected) {
        integer aPtr;
        for ((aPtr = (num_detected - 1)); (aPtr > -1); (--aPtr)) {
            if ((!(~llListFindList(vList,((list)llDetectedName(aPtr)))))) {
                (vList += ([llGetTimestamp()] + [llDetectedName(aPtr)]));
                jump dekal;
            }
        }
        @dekal;
    }
 
    dataserver(key query_id,string data) {
        if ((query_id != aQuery)) return;
        if ((data == EOF)) return;
        (aList += ((list)data));
        (aQuery = llGetNotecardLine("Access List",(++comHandle)));
    }
}

Yet another variation on the same script is one which re-logs visitors. For example, you have a shop and a customer visits your shop. The scripts above would log their first visit but they would not log a second visit to the shop. The version below will re-log visitors if they visit the shop again after a configurable time difference. So, in the previous example, if that visitor leaves and comes back any time after a configurable amount of time, the script below will log that visitor again.

The factory default for re-logging visitors is 30 minutes. To change that, simply change the line:

integer REVISIT_INTERVAL = 30; 

Please note that the time is in minutes. For example, to change it to a one hour interval, you would modify that line:

integer REVISIT_INTERVAL = 60; 

Script with re-logging of visitors:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
// Time in minutes before a a visiting avatar is considered
// a revisiting avatar. For example, when avatars come back
// after this time interval, they will be logged again.
integer REVISIT_INTERVAL = 30; 
 
list vList;
list aList;
 
key aQuery;
 
integer comHandle = 0;
integer comChannel;
 
integer scanInterval = 2;
integer scanRange = 10;
 
/////////////////////////////////////////////////////////////////////////////
// The following two are two nice nifty little functions which I didn't    //
// feel like implementing. They are very useful and  can both be found at: //
// http://wiki.secondlife.com/wiki/Stamp2UnixInt and                       //
// http://wiki.secondlife.com/wiki/ListReverse respectively.               //
/////////////////////////////////////////////////////////////////////////////
/*//--                       Anti-License Text                         --//*/
/*//     Contributed Freely to the Public Domain without limitation.     //*/
/*//   2009 (CC0) [ http://creativecommons.org/publicdomain/zero/1.0 ]   //*/
/*//  Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ]  //*/
list uListReverse( list vLstSrc ){
    integer vIntCnt = (vLstSrc != []);
    while (vIntCnt){
        vLstSrc += llList2List( vLstSrc, (vIntCnt = ~-vIntCnt), vIntCnt );
        vLstSrc = llDeleteSubList( vLstSrc, vIntCnt, vIntCnt );
    }
    return vLstSrc;
}
 
integer uStamp2UnixInt( list vLstStp ){
    integer vIntYear = llList2Integer( vLstStp, 0 ) - 1902;
    integer vIntRtn;
    if (vIntYear >> 31 | vIntYear / 136){
        vIntRtn = 2145916800 * (1 | vIntYear >> 31);
    }else{
        integer vIntMnth = ~-llList2Integer( vLstStp, 1 );
        integer vIntDays = ~-llList2Integer( vLstStp, 2 );
        vIntMnth = llAbs( (vIntMnth + !~vIntMnth) % 12 );
        vIntRtn = 86400 * ((integer)(vIntYear * 365.25 + 0.25) - 24837 +
          vIntMnth * 30 + (vIntMnth - (vIntMnth < 7) >> 1) + (vIntMnth < 2) -
          (((vIntYear + 2) & 3) > 0) * (vIntMnth > 1) +
          llAbs( (vIntDays + !~vIntDays) % 31 ) ) +
          llAbs( llList2Integer( vLstStp, 3 ) % 24 ) * 3600 +
          llAbs( llList2Integer( vLstStp, 4 ) % 60 ) * 60 +
          llAbs( llList2Integer( vLstStp, 5 ) % 60 );
    }
    return vIntRtn;
}
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
 
default {
 
    state_entry() {
        (aQuery = llGetNotecardLine("Access List",comHandle));
        llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
    }
 
    changed(integer change) {
        if ((change & 1)) {
            (aList = []);
            (comHandle = 0);
            (aQuery = llGetNotecardLine("Access List",comHandle));
        }
    }
 
    touch_start(integer total_number) {
        if ((!(~llListFindList(aList,((list)llDetectedName(0)))))) return;
        (comChannel = ((((integer)("0x" + llGetSubString(((string)llGetKey()),-8,-1))) & 1073741823) ^ -1073741825));
        (comHandle = llListen(comChannel,"",llDetectedKey(0),""));
        llDialog(llDetectedKey(0),"\n[K] Visitor list: Please select an option. \n",["Reset","View","Range","Interval"],comChannel);
    }
 
    listen(integer chan,string name,key id,string mes) {
        if ((mes == "View")) {
            jump ztell;
        }
        if ((mes == "Reset")) {
            (vList = []);
            jump zout;
        }
        if ((mes == "Interval")) {
            llDialog(id,"\n[K] Visitor list: Please select a scanning interval in seconds.\n",["2s","4s","8s","16s","32s","64s"],comChannel);
            jump zin;
        }
        if ((((integer)mes) && (!(((integer)mes) & (((integer)mes) - 1))))) {
            (scanInterval = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        if ((mes == "Range")) {
            llDialog(id,"\n[K] Visitor: Please select a scanning distance in meters.\n",["5","10","15","30","35","40","45","50","60","70","80","90"],comChannel);
            jump zin;
        }
        if (((((integer)mes) % 5) == 0)) {
            (scanRange = ((integer)mes));
            llSensorRepeat("",NULL_KEY,1,scanRange,3.14159274,scanInterval);
            jump zout;
        }
        jump zin;
        @ztell;
        integer aPtr;
        llInstantMessage(id,"----------------------- Visitors -------------------------");
        for ((aPtr = (llGetListLength(vList) - 1)); (aPtr > -1); (aPtr -= 2)) {
            llInstantMessage(id,((llList2String(vList,aPtr) + " @ ") + llList2String(vList,(aPtr - 1))));
            llInstantMessage(id,"------------------------------------------------------------");
        }
        @zout;
        llListenRemove(comHandle);
        @zin;
    }
 
    sensor(integer num_detected) {
        integer aPtr;
        for ((aPtr = (num_detected - 1)); (aPtr > -1); (--aPtr)) {
            if (!~llListFindList(vList,(list)llDetectedName(aPtr))) {
                vList += [llGetTimestamp()] + [llDetectedName(aPtr)];
                jump dekal;
            }
 
            if(uStamp2UnixInt(llParseString2List(llGetTimestamp(),["-",":"],["T"]))-uStamp2UnixInt(llParseString2List(llList2String(uListReverse(vList), llListFindList(uListReverse(vList), (list)llDetectedName(aPtr))+1),["-",":"],["T"])) > REVISIT_INTERVAL) {
                vList += [llGetTimestamp()] + [llDetectedName(aPtr)];
            }
@dekal;
        }
    }
 
    dataserver(key query_id,string data) {
        if ((query_id != aQuery)) return;
        if ((data == EOF)) return;
        (aList += ((list)data));
        (aQuery = llGetNotecardLine("Access List",(++comHandle)));
    }
}