Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

General Menu Library

Zonax Delorean
Registered User
Join date: 5 Jun 2004
Posts: 767
05-10-2005 12:15
I am trying to motivate more people to use menus (llDialog's) than llListens and chats, it's more user friendly and easier, too, for the user.

I just made a MenuEngine, that processes a "menudefs" file, which contains definitions for all the menus an application could need (but you can overload it, though, and it will run out of memory :-(

The theory behind the "menudefs" file is:
  1. You have different menus, each has a menuid (string, NO SPACES)
  2. You can "invoke" a menu with doMenu(userKey,menuname); from your script
  3. The menu 'runs', it either returns the option, or goes on to other submenus automatically
  4. You get the result with a link message, specifying the menuid and the result, eg. "CLITEM|Vote 1"


A menudefs item looks like this:
CODE

MENU DEFAULT
TEXT ~ Main Menu ~ Select a function
TOMENU SETTITLE Set title
OPTION Start vote
TOMENU CLITEM Clear Item
OPTIONASK Reset


Where:
  1. MENU <MENUID> -- starts a menu definition, specifying Menu ID
  2. TEXT <messageText> -- the text the user sees on the dialog
  3. TOMENU <MENUID> <ButtonText> -- displays ButtonText on a button and upon pressing, go to menu MENUID
  4. OPTION <ButtonText> -- allows user to press a button thereby returning a signal (result)
  5. OPTIONASK <ButtonText> -- same as OPTION, but asks for Yes/No confirmation, eg. in case of 'reset' button


Usage:
1. Place the scripts and the menudefs in an object
2. RESET the MenuEngine script!!! (Sorry, no auto-reload yet :-)
3. Touch the object :-)

I'll try to document it more, until then, please submit any errors and comments to the Script Discussion Forum!

The MenuEngine script, put in object:
CODE

// Menu Engine - v 1.0.0
// by Zonax Delorean
// License: BSD, but please provide patches/bugfixes if you can!

integer LM_DOMENU = 10001;
integer LM_OPTIONDATA = 10002;
integer LM_MENULOADED = 10003;
integer LM_RESETMENUSYSTEM = 10004;
integer LM_READSTRING = 10005;
integer LM_READSTRINGDATA = 10006;

string confNoteName = "menudefs";
integer cLine;
key cqid;

integer MC_TEXT = 1;
integer MC_TOMENU = 2;
integer MC_OPTION = 3;
integer MC_OPTIONASK = 4;
integer MC_END = 999;
// ...

integer numMenus;
list menudata;
list menuOffsets;
integer MENUREC_SIZE = 4;

integer debug = FALSE;

emitMsgSender( integer num, string msg ) {
if (debug) debugSay("Emit MSG: chn#"+(string)num+" : "+msg);
llMessageLinked(llGetLinkNumber(),num,msg,cUser);
}
emitMsgGlobal( integer num ) {
if (debug) debugSay("Emit MSG Global: chn#"+(string)num+" : ");
llMessageLinked(llGetLinkNumber(),num,"",NULL_KEY);
}

debugSay( string st ) {
llSay(0,st);
}

string clMenuID;
list clButtons;
list clActions;
string clMenuText;

// read data
startReadConfig() {
numMenus = 0;
menudata = [];
menuOffsets = [];
clMenuID = "";
clButtons = [];
clActions = [];
clMenuText = "";

cLine = 1;
cqid = llGetNotecardLine( confNoteName, cLine - 1 ); // request first line
}

commitEntry() {
// [ menuID, startOffset, startOffsetActions, endOffset ]
integer sofs = llGetListLength(menudata);
menuOffsets += [ clMenuID, sofs, sofs + 1 + llGetListLength(clButtons), sofs + 1 + llGetListLength(clButtons) + llGetListLength(clActions) ];
menudata += [ clMenuText ];
menudata += clButtons;
menudata += clActions;

clMenuID = "";
clMenuText = "";
clButtons = [];
clActions = [];
}

readConfigLine( string data ) {
if (data != EOF) {
integer spos = llSubStringIndex(data," ");
if (spos==-1) jump LreadNextLine; // not a line with space

string cmd = llGetSubString( data, 0, spos - 1);
data = llGetSubString( data, spos+1, -1);

integer cmdid = llListFindList(["MENU","TEXT","TOMENU","OPTION","OPTIONASK"],[cmd]);
if (cmdid==-1) jump LreadNextLine; // not a command

if (cmdid==0) { // MENU
if (clMenuID!="") commitEntry();
clMenuID = data;
}
else if (cmdid==1) { // TEXT
if (clMenuText!="") clMenuText += "\n";
list tmpl = llParseString2List(data,["\\n"],[]);
data = llDumpList2String(tmpl,"\n");
clMenuText += data;
}
else if (cmdid==2) { // TOMENU <MenuID> <ButtonText> or TOMENU <MenuID>
integer wpos = llSubStringIndex(data," ");
string tomenuid = data;
string btext = data;
if (wpos>=0) {
tomenuid = llGetSubString(data,0,wpos - 1);
btext = llGetSubString(data,wpos+1,-1);
}

clButtons += [ btext ];
clActions += [ MC_TOMENU, tomenuid ];
}
else if (cmdid==3) { // OPTION <ButtonText>
clButtons += [ data ];
clActions += [ MC_OPTION, data ];
}
else if (cmdid==4) { // OPTIONASK <ButtonText>
clButtons += [ data ];
clActions += [ MC_OPTIONASK, data ];
}

@LreadNextLine; // jump point
cLine++;
cqid = llGetNotecardLine( confNoteName, cLine - 1 );
} else {
numMenus = llGetListLength(menuOffsets) / MENUREC_SIZE;
configLoaded();
}
}

//
// gets called when the config is done loading
//
configLoaded() {
if (debug) {
debugSay("Menu data loaded, mem: "+(string)llGetFreeMemory());
llInstantMessage(llGetOwner(),"OFS: "+llDumpList2String(menuOffsets,","));
llInstantMessage(llGetOwner(),"DATA: "+llDumpList2String(menudata,","));
}

// signal a message to the main prog
emitMsgGlobal(LM_MENULOADED);
}

string cMenuName;
integer cMenuNum;
integer CHATBASECHANNEL = 100050;
integer cOfs; // current menu start offset
key cUser; // current user
integer nList;
integer isListening = FALSE;
float RESPONSE_TIMEOUT = 60.0;
list cButtons;
list cActions;
integer cMenuType;
integer MT_ASK = 2;
integer MT_NORMAL = 1;
integer MT_READSTRING = 3;
integer cSenderID;

clearListens() {
if (isListening) {
llListenRemove(nList);
isListening = FALSE;
}
}

integer getMenuNum( string menuid ) {
integer c;
for (c=0;c<numMenus;c++)
if (llList2String(menuOffsets,c*MENUREC_SIZE) == menuid)
return(c);
return(-1);
}

// load a data for a menu and start a dialog
doMenu( key user, string menuname ) {
if (debug) debugSay("Do menu: "+menuname);
integer mnum = getMenuNum(menuname);
if ( mnum == -1 ) { // menu not found
llSay(0,"Error: no such menu: "+menuname);
return;
}

cOfs = llList2Integer( menuOffsets, mnum * MENUREC_SIZE + 1 );
integer actOfs = llList2Integer( menuOffsets, mnum * MENUREC_SIZE + 2 );
integer actEnd = llList2Integer( menuOffsets, mnum * MENUREC_SIZE + 3 );

cMenuNum = mnum;
cMenuName = menuname;
cUser = user;
cButtons = llList2List( menudata, cOfs+1, actOfs - 1 );
cActions = llList2List( menudata, actOfs, actEnd );
cMenuType = MT_NORMAL; // normal menu

llDialog( user, llList2String( menudata, cOfs ), cButtons, CHATBASECHANNEL + cMenuNum );
clearListens();
nList = llListen( CHATBASECHANNEL + cMenuNum, "", user, "");
isListening = TRUE;
llSetTimerEvent(RESPONSE_TIMEOUT);
}


// param: <TEXT>
doAskMenu( key user, string param ) {
cMenuType = MT_ASK;
cButtons = [ "Yes", "No" ];
cActions = [ param ];
llDialog( user, param, cButtons, CHATBASECHANNEL + cMenuNum );
clearListens();
nList = llListen( CHATBASECHANNEL + cMenuNum, "", user, "");
isListening = TRUE;
llSetTimerEvent(RESPONSE_TIMEOUT);
}

////////////////////////////////////////////
default
{
state_entry()
{
if (debug) llSay(0, "Loading menu... mem: "+(string)llGetFreeMemory());
startReadConfig();
}

dataserver(key query_id, string data) {
if (query_id==cqid) readConfigLine(data);
}

listen( integer channel, string name, key id, string message ) {
llSetTimerEvent(0.0);
clearListens();

if (cMenuType==MT_ASK) {
if (message=="Yes") {
string param = llList2String( cActions, 0 ); // special case
emitMsgSender(LM_OPTIONDATA,llDumpList2String([ cMenuName, param ],"|"));
}
return;
}
if (cMenuType==MT_READSTRING) {
string param = llList2String( cActions, 0 ); // special case
emitMsgSender(LM_READSTRINGDATA,llDumpList2String([ param, message ],"|"));
return;
}

integer c;
integer nb = llGetListLength(cButtons);
integer fnd = -1;
for (c=0;c<nb;c++) {
if (llList2String(cButtons,c)==message) {
fnd = c;
jump doneSR;
}
}
@doneSR;
if (fnd>=0) {
integer act = llList2Integer( cActions, fnd * 2 );
string param = llList2String( cActions, fnd * 2 + 1 );
if (debug) {
debugSay("r: "+(string)act+" "+param);
}
if (act==MC_TOMENU) {
string tomenu = param;
integer spos = llSubStringIndex(tomenu," ");
if (spos>=0) tomenu = llGetSubString(tomenu,0,spos - 1);

doMenu(id,tomenu);
return;
}
if (act==MC_OPTION) {
emitMsgSender(LM_OPTIONDATA,llDumpList2String([ cMenuName, param ],"|"));
return;
}
if (act==MC_OPTIONASK) {
doAskMenu(id,param);
return;
}

}
}

link_message(integer sender_num, integer num, string str, key id) {
if (num==LM_DOMENU) {
cSenderID = sender_num;
doMenu(id,str);
return;
}
if (num==LM_READSTRING) {
cSenderID = sender_num;
cMenuType = MT_READSTRING;

string message = str;
string retval = str;
integer spos = llSubStringIndex(str," ");
if (spos>=0) {
message = llGetSubString( str, spos+1, -1 );
retval = llGetSubString( str, 0, spos - 1 );
}
cActions = [ retval ];
nList = llListen( 0, "", id, ""); // listen from the user only
isListening = TRUE;
llSay(0,message);
llSetTimerEvent(RESPONSE_TIMEOUT);
return;
}
if (num==LM_RESETMENUSYSTEM) {
llResetScript();
}
}

timer() {
llSetTimerEvent(0.0);
clearListens();
}
}


An example menu system, name it "menudefs" and put in same object:
CODE

MENU DEFAULT
TEXT ~ Main Menu ~ Select a function
TOMENU SETTITLE Set title
OPTION Start vote
TOMENU CLITEM Clear Item
OPTIONASK Reset

MENU SETTITLE
TEXT Set title for which item?
OPTION 1
OPTION 2
OPTION 3
OPTION 4
OPTION 5
OPTION 6
OPTION 7
OPTION 8
OPTION 9

MENU CLITEM
TEXT Clear title and picture for which item?
OPTION 1
OPTION 2
OPTION 3
OPTION 4
OPTION 5
OPTION 6
OPTION 7
OPTION 8
OPTION 9

MENU INVOTE
TEXT Voting in progress...
OPTIONASK Clear votes
OPTIONASK Close voting

MENU VOTEDONE
TEXT Vote closed
OPTIONASK Restart voting
OPTION Tell results


An example really small test script with the "Interface Library", name it "MenuTest" and put it in the object:
CODE

// Menu demo program


//---Menu Library-------------
integer LM_DOMENU = 10001;
integer LM_OPTIONDATA = 10002;
integer LM_MENULOADED = 10003;
integer LM_RESETMENUSYSTEM = 10004;
integer LM_READSTRING = 10005;
integer LM_READSTRINGDATA = 10006;

resetMenu() {
llMessageLinked(llGetLinkNumber(),LM_RESETMENUSYSTEM,"",NULL_KEY);
}
doMenu( key user, string menuname ) {
llMessageLinked(llGetLinkNumber(),LM_DOMENU,menuname,user);
}
readString( key user, string var, string prompt ) {
llMessageLinked(llGetLinkNumber(),LM_READSTRING,var+" "+prompt,user);
}
//---Menu Library End----------

default
{
state_entry()
{
//resetMenu(); -- just when you change menudefs
}

link_message(integer sender_num, integer num, string str, key id) {
if (num==LM_OPTIONDATA) {
llSay(0,"MenuOutput: "+str);
}
if (num==LM_MENULOADED) {
llSay(0,"Menu has just been (re)loaded");
}
if (num==LM_READSTRINGDATA) {
integer wpos = llSubStringIndex(str,"|");
string varname = llGetSubString(str,0,wpos - 1);
string response = llGetSubString(str,wpos+1,-1);
llSay(0,"Variable: "+varname+" String read: "+response);
}
}

touch_start(integer total_number)
{
doMenu(llDetectedKey(0),"DEFAULT");

//also you can try:
//readString(llDetectedKey(0),"FAVCOLOR","Please enter your favorite color in chat:");
}
}
Nada Epoch
The Librarian
Join date: 4 Nov 2002
Posts: 1,423
Discussion Thread
05-13-2005 09:55
/54/d3/46157/1.html
_____________________
i've got nothing. ;)
Kage Seraph
I Dig Giant Mecha
Join date: 3 Nov 2004
Posts: 513
06-30-2005 14:43
*bump* This script is pretty quick to figure out and quite easy to use. If I can have it running in ten minutes, ANYONE can. :D Oh, and it is freaking useful too. Props to Zonax.
fr0b Assia
Registered User
Join date: 6 Jun 2005
Posts: 10
Error?
07-02-2005 17:37
When using your sample code I get a "no such menu: DEFAULT" error.. any help?