User:Kephra Nurmi/lsDialog

From Second Life Wiki
Jump to: navigation, search

lsDialog

lsDialog is an universal notecard driven menu dialog system. lsDialog could be used in a HUD or rezzed objects, providing one prim dialog trees or multi prim button dialogs. You can restrict usage to owner, group, named avatars or everybody. lsDialog is using llSay or llMessageLinked to comunicate with other scripts.

Script: .lsDialog 09124

// .lsDialog 09124 (c) 2007-2009 by Kephra Nurmi under Creative Commons Attribution-Share Alike 3.0 License
 
string configName = ".lsDialog";
string configKey = NULL_KEY;
integer configLine = 0;
key configReq = NULL_KEY;
integer avChannel;
 
list dialogNames = [];
list dialogLines = [];
string currentName = "";
string currentLines = "";
 
string currentMenu = "";
list currentOptions = [];
list currentValues = [];
list simpleOptions = [];
list simpleActions = [];
 
integer djChannel = 0;
integer djListen = 0;
key djKey = NULL_KEY;
string djName = "";
list guests = [ ];
integer everybody = FALSE;
integer samegroup = FALSE;
integer again = TRUE;
integer linked = FALSE;
 
integer permissions(key id) {
    return (everybody
        || (samegroup && llSameGroup(id))
        || (llGetOwner() == id)
        || ((NULL_KEY != djKey) && (djKey == id))
        || (llListFindList(guests, [ llKey2Name(id) ])>=0));
}
 
configRestart() {
    configName = ".lsDialog";
    key conf = llGetInventoryKey(configName);
    if ((string)conf == configKey) return;
    if ((NULL_KEY != configKey) && (llGetFreeMemory() < 6000)) llResetScript();
    dialogNames = [];
    dialogLines = [];
    simpleOptions = [];
    simpleActions = [];
    currentName = "";
    currentLines = "";
    djChannel = 0;
    djKey = NULL_KEY;
    djName = "";
    guests = [ ];
    everybody = FALSE;
    samegroup = FALSE;
    if (conf == NULL_KEY) return;
    configKey = conf;
    configLine = 0;
    configReq = llGetNotecardLine(configName,configLine);
}
 
flushDialog() {
    if ("" == currentName) return;
    if ("" == currentLines) return;
    integer i = llListFindList(dialogNames,[ currentName ]);
    if (i>=0) {
        dialogNames = llDeleteSubList(dialogNames,i,i);
        dialogLines = llDeleteSubList(dialogLines,i,i);
    }
    dialogNames = dialogNames + [ currentName ];
    dialogLines = dialogLines + [ currentLines ];
    currentName = "";
    currentLines = "";
}
 
parseDialogRC(string data) {
    if (llStringLength(data) == 0) return;
    if (llGetSubString(data,0,0) == "#") return;
    if ((llGetSubString(data,0,0) == "[") && (llSubStringIndex(data,"]") >= 0)) {
        flushDialog();
        currentName = llGetSubString(data,1,llSubStringIndex(data,"]")-1);
        return;
    }
    if ("" == currentName) {
        if ("Allow=Owner" == data) { everybody = FALSE; samegroup = FALSE; }
        else
        if ("Allow=Group" == data) { everybody = FALSE; samegroup = TRUE; }
        else
        if ("Allow=All" == data) { everybody = TRUE; samegroup = FALSE; }
        else
        if ("Allow=" == llGetSubString(data,0,5)) { guests = guests + [ llGetSubString(data,6,-1) ]; }
        else
        if ("LSDJ=" == llGetSubString(data,0,4)) { djChannel = (integer)llGetSubString(data,5,-1); }
        else
        if ("Dialog=Again" == data) { again = TRUE; }
        else
        if ("Dialog=Once" == data) { again = FALSE; }
        else
        if ("Dialog=Link" == data) { linked = TRUE; }
    } else
    if ("*" == currentName) {
        integer p = llSubStringIndex(data,"=");
        if (0 < p) {
            simpleOptions = simpleOptions + [ llGetSubString(data, 0, p-1) ];
            simpleActions = simpleActions + [ llGetSubString(data, p+1, -1) ];
        }
    } else
    if (llSubStringIndex(data,"=") >= 0) {
        if ("" == currentLines)
            currentLines = data;
        else
            currentLines = currentLines+"\n"+data;
    }
}
 
parseDialogLines(string menu) {
    currentMenu = menu;
    currentOptions = [];
    currentValues = [];
    integer i = llListFindList(dialogNames, [ menu ]);
    if (i < 0) return;
    list l = llParseString2List(llList2String(dialogLines,i),[ "\n" ], []);
    integer n = llGetListLength(l);
    integer k;
    string s;
    for (i = 0; i<n; i++) {
        s = llList2String(l, i);
        k = llSubStringIndex(s,"=");
        if (k>0) {
            currentOptions = currentOptions + [ llGetSubString(s,0,k-1) ];
            currentValues = currentValues + [ llGetSubString(s,k+1,-1) ];
        }
    }
}
 
action(string s, key id) {
    integer q = -1;
    integer r = -1;
    q = llSubStringIndex(s," ");
    if (0 <  q) { r = q+llSubStringIndex(llGetSubString(s,q+1,-1)," ")+1; }
    if (q <  r) {
        string cmd = llGetSubString(s,0,q-1);
        integer ch = (integer)llGetSubString(s,q+1,r-1);
        string val = llGetSubString(s,r+1,-1);
        if ("say" == cmd) llSay(ch,val);
        if ("shout" == cmd) llShout(ch,val);
        if ("whisper" == cmd) llWhisper(ch,val);
        if ("region" == cmd) llRegionSay(ch,val);
        if ("root" == cmd) llMessageLinked(LINK_ROOT,ch,val,id);
        if ("link" == cmd) llMessageLinked(LINK_SET,ch,val,id);
        if ("this" == cmd) llMessageLinked(LINK_THIS,ch,val,id);
    }
}
 
default {
    state_entry() {
        configRestart();
        avChannel = 1000+(integer)llFrand(3000.0);
        llListen( avChannel, "", NULL_KEY, "" );
    }
    changed(integer type) {
        if (type & CHANGED_OWNER) llResetScript();
        if (type & CHANGED_INVENTORY) configRestart();
    }
    dataserver(key queryId, string data) {
        if (queryId != configReq) return;
        if (data != EOF) {
            llSetText("parsing line "+(string)configLine,<1,0,0>,1);
            parseDialogRC(data);
            configReq = llGetNotecardLine(configName,++configLine);
        } else {
            if (0 != djListen)
                llListenRemove(djListen);
            if (0 != djChannel)
                djListen = llListen( djChannel, "", NULL_KEY, "" );
            llSetText("",<1,0,0>,1);
            flushDialog();
            llOwnerSay("ready, "+(string)llGetFreeMemory()+" free memory, listen on channel: "+(string)avChannel);
        }
    }
    touch_start(integer num) {
        key id = llDetectedKey(0);
        if (!permissions(id)) return;
        if (linked) {
            integer l = llDetectedLinkNumber(0);
            integer p = llListFindList(simpleOptions, [ llGetLinkName(l) ]);
            if (0 <= p)
                action(llList2String(simpleActions, p), id);
            else {
                p = llListFindList(dialogNames, [ llGetLinkName(l) ]);
                if (p >= 0) {
                    parseDialogLines(llList2String(dialogNames,p));
                    llDialog(id,llList2String(dialogNames,p),currentOptions,avChannel);
                }
            }
        } else
        if ((llGetListLength(dialogNames) > 1) || (llGetListLength(simpleActions) > 0)) {
            currentMenu = "";
            string s = llGetObjectDesc();
            if ("" == s) s = "your command?";
            llDialog(id,s,dialogNames+simpleOptions,avChannel);
        } else
        if (llGetListLength(dialogNames) == 1) {
            parseDialogLines(llList2String(dialogNames,0));
            llDialog(id,llList2String(dialogNames,0),currentOptions,avChannel);
        }
    }
    listen(integer channel, string name, key id, string message) {
        if (djChannel == channel) {
            key k = id;
            if (llGetAgentInfo(id)<=0) k = llGetOwnerKey(k);
            if (k != llGetOwner()) return;
            list tmp = llCSV2List(message);
            if ("DJ" == llList2String(tmp,0))
                djKey = llList2String(tmp,1);
            return;
        }
        if (!permissions(id)) return;
        if ("" == currentMenu) {
            integer i = llListFindList(simpleOptions,[ message ]);
            if (i>=0) action(llList2String(simpleActions, i), id);
            else {
                parseDialogLines(message);
                llDialog(id,currentMenu,currentOptions + [ "Back" ],avChannel);
            }
        } else 
        if ("Back" == message) {
            currentMenu = "";
            string s = llGetObjectDesc();
            if ("" == s) s = "your command?";
            llDialog(id,s,dialogNames+simpleOptions,avChannel);
        } else {
            integer i = llListFindList(currentOptions,[ message ]);
            if (i>=0) action(llList2String(currentValues, i), id);
            if (again) {
                if (llGetListLength(dialogNames) > 1)
                    llDialog(id,currentMenu,currentOptions + [ "Back" ],avChannel);
                else
                    llDialog(id,currentMenu,currentOptions,avChannel);
            }
        }
    }
}

Notecard: .lsDialog

The .lsDialog notecard defines the menu dialog tree. A typical simple notecard could look like:

# default is to ask repeating ... remove comment if 
#Dialog=Once
#Dialog=Link
 
# allow only one of the following
#Allow=Owner
#Allow=Group
#Allow=All
 
# and in addition several named avatars
#Allow=Kephra Nurmi
#Allow=Alez Ember
 
[*]
Reset=link 0 reset
 
[Size]
90%=link 0 *,size,90
95%=link 0 *,size,95
100%=link 0 *,size,100
105%=link 0 *,size,105
110%=link 0 *,size,110
 
[Color]
Lime=link 0 glass,color,Lime
Ivory=link 0 glass,color,Ivory
Cyan=link 0 glass,color,Cyan

This notecard will define a single prim dialog system with one top menu called 'Reset' and two sub menus called 'Size' and 'Color' with link action on channel 0 and default permissions. The object description defines the name for the top menu dialog. Each menu starts with a section header in brackets followed lines to define the dialog entries. Each entry has a name equal to an action, a channel and a string.

Possible actions are:

  • say - llSay(ch,val);
  • shout - llShout(ch,val);
  • whisper - llWhisper(ch,val);
  • region - llRegionSay(ch,val);
  • root - llMessageLinked(LINK_ROOT,ch,val,id);
  • link - llMessageLinked(LINK_SET,ch,val,id);
  • this - llMessageLinked(LINK_THIS,ch,val,id);

The top of the notecard, before any sections in brackets start, defines the permission and behavior. The hash marks lines as comments. Uncommenting Dialog=Once will cause to ask only one once, and not repeatingly. Dialog=Linked would need a 4 prim object, with buttons named 'Reset', 'Size' and 'Color'. The Allow= statements change the default permissions.