Table of Contents

ChangeLog

20 July 2014

  • Code maintenance.

7 March 2013

  • Removed the visitors feature since it was redundant. For a more complete visitors solution, choose one of the visitors scripts and run them separately.
  • Script maintenance.

30 August 2011

  • Group invite added on request for Tray Guisse.

About

This is a standard visitor greeter, compatible with the mail system, which has the following features:

Setup

In order to make this greeter work, you would have to place it somewhere close to the landing point so that new visitors get scanned more efficiently. To set it up, first create a notecard called Access containing the avatar names of administrators line-by-line. For example, if avatar Morgan LeFay and avatar Frank Black should both be able to manage the greeter, the notecard would look like this:

Morgan LeFay
Frank Black

You can add as many administrators as you like to the notecard. Next, configure the script so that it matches your liking. The section in the script that you should change is the one marked with the CONFIGURATION comment:

///////////////////// CONFIGURATION //////////////////
string GREET_MESSAGE = "Hello!";
string DIALOG_MESSAGE = "Welcome, please select from the options below: ";
string SUBSCRIBE_MESSAGE = "Thank you for subscribing! We will keep you updated!";
integer SCAN_RANGE = 10;
integer SCAN_INTERVAL = 2;
string URL = "http://wiki.secondlife.com/wiki/Greeter";
list USER_OPTIONS = ["Landmark", "Subscribe", "URL", "Notecard", "Gift"];
key INVITE_GROUP_KEY = "9092e77a-ed89-508d-30a9-456873423901";
string INVITE_GROUP_MESSAGE = "To join the group, please click the link in your history window (ctrl+h):";
/////////////////////////////////////////////////////
string GREET_MESSAGE = "Hello and welcome to The Chocolate Factory! Please enjoy our cocoa...";
string SCAN_RANGE = 30;
list USER_OPTIONS = ["Landmark", "Subscribe", "URL", "Notecard", "Group", "Gift"];

This means that the greeter will offer landmarks, offer subscriptions, offer to send URLs, offer to send a notecard and offer to send a gift. However, you might not want to send gifts, for example. In that case, you can disable the gift sending feature by simply modifying this line. For example, to disable the gift sending option, you would change this line to:

list USER_OPTIONS = ["Landmark", "Subscribe", "URL", "Notecard", "Group" ];

The final step is to add a landmark, a notecard and perhaps a gift to the prim containing the greeter. When a visitor clicks an option, say "Landmark", the greeter will hand out the first landmark in the prim. The same applies, to notecards and gifts (objects). The greeter does not support sending multiple objects, however you can always bundle a bunch of stuff in an object and send it as a gift.

To connect the greeter with the mail system, an administrator on the access list would have to select Dump Subs. This will make the greeter list all subscribers line by line in the mail system's Subscriber notecard format. For example, from time to time after users have subscribed using the greeter, an administrator would have to click the greeter and select Dump Subs, this will subscribers line-by-line:

1ad33407-a792-476d-a5e3-06007c0802bf#Morgan LeFay
82c73d6d-facf-4332-b17b-06d8cd66a116#Frank Black

The administrator will copy these lines and paste them into the mail system Subscriber notecard in order to make the mail system work.

Hacks

list USER_OPTIONS = ["Landmark", "Subscribe", "URL", "Notecard", "Gift", "Group"];

to:

list USER_OPTIONS = ["Landmark", "Subscribe", "URL", "Rules", "Gift", "Group"];

as well as change the line:

if(message == "Notecard") {

to:

if(message == "Rules") {

and proceed the same with every button you wish to change.

Code

This script was tested and works on OpenSim version 0.7.4!

greeter.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//                            CONFIGURATION                              //
///////////////////////////////////////////////////////////////////////////
 
string GREET_MESSAGE = "Hello!";
string DIALOG_MESSAGE = "Welcome, please select from the options below: ";
string SUBSCRIBE_MESSAGE = "Thank you for subscribing! We will keep you updated!";
string SUBSCRIBE_ALREADY_SUBSCRIBED = "Thank you, you are already subscribed.";
integer SCAN_RANGE = 10;
integer SCAN_INTERVAL = 2;
string URL = "http://grimore.org/secondlife:greeter";
list USER_OPTIONS = ["Landmark", "Subscribe", "URL", "Notecard", "Gift", "Group"];
key INVITE_GROUP_KEY = "9092e87a-ed89-509d-30a9-456873423901";
string INVITE_GROUP_MESSAGE = "To join the group, please click the link in your history window (ctrl+h):";
 
///////////////////////////////////////////////////////////////////////////
//                              INTERNALS                                //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
    if (llStringLength(data) == 0) return "";
    if (llStringLength(k) == 0) return "";
    list a = llParseStringKeepNulls(data, ["&", "="], []);
    integer i = llListFindList(a, [ k ]);
    if (i != -1) return llList2String(a, i + 1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
    if (llStringLength(data) == 0) return k + "=" + v;
    if (llStringLength(k) == 0) return "";
    if (llStringLength(v) == 0) return "";
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []),
            0, -1, 2
            ),
        [ k ]);
    if (i != -1) return llDumpList2String(
            llListReplaceList(
                llParseString2List(data, ["&"], []),
                [ k + "=" + v ],
                i, i),
            "&");
    return data + "&" + k + "=" + v;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueDelete(string k, string data) {
    if(llStringLength(data) == 0) return "";
    if(llStringLength(k) == 0) return "";
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []), 
            0, -1, 2
        ), 
    [ k ]);
    if(i != -1) return llDumpList2String(
        llDeleteSubList(
            llParseString2List(data, ["&"], []),
        i, i), 
    "&");
    return data;
}
 
list aList = [];
list kList = [];
list sList = [];
 
list dialogs = [];
integer line = 0;
 
default {
    state_entry() {
        if (llGetInventoryType("Access") != INVENTORY_NOTECARD) {
            llSay(DEBUG_CHANNEL, "Unable to a notecard named Access.");
            return;
        }
        aList = [];
        llGetNotecardLine("Access", line);
    }
 
    dataserver(key query_id, string data) {
        if (data == EOF) state scan;
        if (data == "") jump continue;
        aList += (list)data;
@continue;
        llGetNotecardLine("Access", ++line);
    }
 
    changed(integer change) {
        if (change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state scan {
    state_entry() {
        llSensorRepeat("", "", AGENT, SCAN_RANGE, PI, SCAN_INTERVAL);
    }
 
    timer() {
        integer handles = llGetListLength(dialogs) - 1;
        do {
            llListenRemove(llList2Integer(dialogs, handles));
        } while (--handles > -1);
    }
 
    touch_start(integer num) {
        key agent = llDetectedKey(0);
        integer channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
        integer com = llListen(channel, "", agent, "");
        if (llListFindList(dialogs, (list)com) == -1) dialogs += com;
        if (llListFindList(aList, (list)llDetectedName(0)) != -1) {
            llDialog(agent, "Welcome to the greeter control, please choose your configuration options: \n", [ "Visitors", "Dump Subs", "Statistics", "Full Reset" ], channel);
            jump time;
        }
        llDialog(agent, DIALOG_MESSAGE, USER_OPTIONS, channel);
@time;
        llSetTimerEvent(60);
    }
 
    listen(integer channel, string name, key id, string message) {
        if (message == "Group") {
            llInstantMessage(id, INVITE_GROUP_MESSAGE + "\n secondlife:///app/group/" + (string)INVITE_GROUP_KEY + "/about");
            jump menu;
        }
        if (message == "Landmark") {
            if (llGetInventoryNumber(INVENTORY_LANDMARK) == 0) {
                llInstantMessage(id, "Sorry, no landmark is available at this time.");
                jump menu;
            }
            llGiveInventory(id, llGetInventoryName(INVENTORY_LANDMARK, 0));
            llSetObjectDesc(
                wasKeyValueSet(
                    "landmarks",
                    (string)(
                        (integer)(
                            wasKeyValueGet("landmarks", llGetObjectDesc())
                            ) + 1
                        ),
                    llGetObjectDesc()
                    )
                );
            jump menu;
        }
        if (message == "Gift") {
            if (llGetInventoryNumber(INVENTORY_OBJECT) == 0) {
                llInstantMessage(id, "Sorry, no gift is available at this time.");
                jump menu;
            }
            llGiveInventory(id, llGetInventoryName(INVENTORY_OBJECT, 0));
            llSetObjectDesc(
                wasKeyValueSet(
                    "gifts",
                    (string)(
                        (integer)(
                            wasKeyValueGet("gifts", llGetObjectDesc())
                            ) + 1
                        ),
                    llGetObjectDesc()
                    )
                );
            jump menu;
        }
        if (message == "Notecard") {
            integer notecards = llGetInventoryNumber(INVENTORY_NOTECARD) - 1;
            do {
                if (llGetInventoryName(INVENTORY_NOTECARD, notecards) != "Access") {
                    llGiveInventory(id, llGetInventoryName(INVENTORY_NOTECARD, notecards));
                    llSetObjectDesc(
                        wasKeyValueSet(
                            "notecards",
                            (string)(
                                (integer)(
                                    wasKeyValueGet("notecards", llGetObjectDesc())
                                    ) + 1
                                ),
                            llGetObjectDesc()
                            )
                        );
                }
            } while (--notecards > -1);
            llInstantMessage(id, "Sorry, no notecard is available at this time.");
            jump menu;
        }
        if (message == "URL") {
            llInstantMessage(id, URL);
            llSetObjectDesc(
                wasKeyValueSet(
                    "urls",
                    (string)(
                        (integer)(
                            wasKeyValueGet("urls", llGetObjectDesc())
                            ) + 1
                        ),
                    llGetObjectDesc()
                    )
                );
            jump menu;
        }
        if (message == "Subscribe") {
            if (llListFindList(sList, (list)id) == -1) {
                sList += (list)id + (list)name;
                llInstantMessage(id, SUBSCRIBE_MESSAGE);
                jump menu;
            }
            llInstantMessage(id, SUBSCRIBE_ALREADY_SUBSCRIBED);
            jump menu;
        }
 
        if (llListFindList(aList, (list)name) == -1) return;
 
        if (message == "Dump Subs") {
            integer subscribers = llGetListLength(sList) - 1;
            if(subscribers == -1) {
                llInstantMessage(id, "No subscribers yet.");
                jump time;
            }
            do {
                llInstantMessage(id, llList2String(sList, subscribers) + "#" + llList2String(sList, subscribers - 1));
                subscribers -= 2;
            } while (subscribers > -1);
            jump time;
        }
 
        if(message == "Visitors") {
            llInstantMessage(id, "Visitors: " + (string)((integer)wasKeyValueGet("visitors", llGetObjectDesc())));
            jump time;
        }
 
        if (message == "Statistics") {
            llInstantMessage(id, "---------------------- Statistics ------------------------");
            llInstantMessage(id, "Total number of subscribers: " + 
                (string)(llGetListLength(sList) / 2)
            );
            llInstantMessage(id, "Total number of landmarks handed out: " + 
                (string)(
                    (integer)wasKeyValueGet("landmarks", llGetObjectDesc())
                )
            );
            llInstantMessage(id, "Total number of gifts handed out: " + 
                (string)(
                    (integer)wasKeyValueGet("gifts", llGetObjectDesc())
                )
            );
            llInstantMessage(id, "Total number of notecards handed out: " + 
                (string)(
                    (integer)wasKeyValueGet("notecards", llGetObjectDesc())
                )
            );
            llInstantMessage(id, "Total number of URLs requested: " + 
                (string)(
                    (integer)wasKeyValueGet("urls", llGetObjectDesc())
                )
            );
            llInstantMessage(id, "------------------------------------------------------------");
            jump time;
        }
 
        if (message == "Full Reset") {
            list reset = [ "landmarks", "gifts", "notecards", "urls", "visitors" ];
            do {
                string item = llList2String(reset, 0);
                llSetObjectDesc(wasKeyValueDelete(item, llGetObjectDesc()));
                reset = llDeleteSubList(reset, 0, 0);
            } while(llGetListLength(reset));
            llInstantMessage(id, "The greeter is now resetting...");
            llResetScript();
        }
 
        return;
@menu;
        llDialog(id, DIALOG_MESSAGE, USER_OPTIONS, channel);
@time;
        llSetTimerEvent(60);
    }
 
    sensor(integer num) {
        --num;
        do {
            key agent = llDetectedKey(num);
            if (llListFindList(kList, (list)agent) != -1) jump continue;
            if (llGetFreeMemory() < 2048) kList = llDeleteSubList(kList, 0, 0);
            kList += agent;
            llSetObjectDesc(
                wasKeyValueSet(
                    "visitors",
                    (string)(
                        (integer)(
                            wasKeyValueGet("visitors", llGetObjectDesc())
                            ) + 1
                        ),
                    llGetObjectDesc()
                    )
                );
            llInstantMessage(agent, GREET_MESSAGE);
            integer channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
            integer com = llListen(channel, "", agent, "");
            if (llListFindList(dialogs, (list)com) == -1) dialogs += com;
            llDialog(agent, DIALOG_MESSAGE, USER_OPTIONS, channel);
@continue;
        } while (--num > -1);
    }
 
    changed(integer change) {
        if (change & CHANGED_INVENTORY) llResetScript();
    }
}