Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Discussion: 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
Original Thread
05-13-2005 09:55
/15/4a/45762/1.html
_____________________
i've got nothing. ;)
Zonax Delorean
Registered User
Join date: 5 Jun 2004
Posts: 767
05-13-2005 11:23
Sorry, but I grew a bit impatient waiting for the Scripting Library moderation :-))

So I started another thread, please use that for replies!

Also, the WIKI page with the same code (and in the future, newer versions :-)
General Menu Engine in the Wiki
Another example:
Simple example in the Wiki
Kage Seraph
I Dig Giant Mecha
Join date: 3 Nov 2004
Posts: 513
07-01-2005 11:28
*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.
Wheel Fizz
Registered User
Join date: 26 May 2005
Posts: 36
07-03-2005 02:58
Thanks just what I was looking for...freaky :o...:)
Ameretto Fredericks
Registered User
Join date: 11 Apr 2004
Posts: 48
Menu Library
09-26-2006 04:20
Hi

I the total novice tried this and received this message:

menu library test: Error: no such menu: DEFAULT

:confused:
_____________________
Second Life....My First Home.

Ameretto's Designs @ SL Exchange

Ameretto's Designs @ SL Boutique

My Main Store In Game
Selby 217,35

Ameretto Fredericks
Registered User
Join date: 11 Apr 2004
Posts: 48
Thank You!!!
09-26-2006 15:33
Thank you so much for helping me!!! :D
It was very kind of you to take time out of your busy schedule to help
me and i truly appreicate it!!

THUMBS UP!!!
_____________________
Second Life....My First Home.

Ameretto's Designs @ SL Exchange

Ameretto's Designs @ SL Boutique

My Main Store In Game
Selby 217,35

Andy Enfield
Hippo Technologies CEO
Join date: 22 Nov 2005
Posts: 79
09-27-2006 02:56
Neat. I'd recommend sticking it in a separate script to the main script in whatever you're working on and use llMessageLinked() to pass the details of the menu to call and the UUID of the agent who needs to see the menu.

Good job!
Katanya Mistral
The Lonely Tigress
Join date: 6 Oct 2005
Posts: 87
11-10-2006 07:03
I'm getting the same error Ameretto mentioned above. Is there any chance you could post the solution for that here so everyone that encounters it can find it easier?

Thanks! =)
_____________________
Visit my website: Katanya.net
My SL site: Kitty's SL Adventures
Joannah Cramer
Registered User
Join date: 12 Apr 2006
Posts: 1,539
11-10-2006 07:17
From: Katanya Mistral
I'm getting the same error Ameretto mentioned above. Is there any chance you could post the solution for that here so everyone that encounters it can find it easier?

If you copy/paste sample notecard directly from this thread, it will wind up with trailing spaces... which causes the script to create menu named "DEFAULT " rather than "DEFAULT" etc. Same for the other buttons.

You'll have to either manually remove the trailing spaces, or to modfiy the original script so it does so automatically when it processes the config card.
Kornscope Komachi
Transitional human
Join date: 30 Aug 2006
Posts: 1,041
DEFAULT message again
11-28-2006 18:54
Hi folks, I've just started getting to grips with some scripting and can't get passed the can't find menu Default.
I followed the above and poked around but don't want to mess it up before it works.
What else can I check?
_____________________
SCOPE Homes, Bangu
-----------------------------------------------------------------
Thanatos Rhiadra
Registered User
Join date: 10 Jul 2008
Posts: 1
01-19-2009 20:49
I am having the same problem. I can't get past the CAN'T FIND MENU DEFAULT error. It works fine with the test notecard, but when I put my own in, it fails. I've turned on debug in the menuengine, and it appears that it reads all the notecard lines, but the data is empty at the end of it.

I'm stumped.

Here is my menudefs (no trailing spaces)

MENU DEFAULT
TEXT ~ Main Menu ~ Select a function
OPTION Lock
OPTION Unlock
OPTION Open
OPTION Close
Basement Desade
Registered User
Join date: 14 Jul 2006
Posts: 91
07-05-2009 18:31
From: Thanatos Rhiadra
I am having the same problem. I can't get past the CAN'T FIND MENU DEFAULT error.


Well, as long as we are sporadically bumping old threads, I was having the same problem with this until I realized that for some reason simply resetting the script wasn't working. I even got this error when I replaced my new notecard with the original.

I subsequently discovered that what worked was to edit the scripts just enough to make the save button available, (like adding a space, then deleting it) and saving them. Then the scripts were happy with my new notecards.

Anyone else out there with experience actually using this? I'm just now playing with it, and while I can get it to "work," it's slow going actually getting it to DO anything, mostly due to my scripting ineptitude, I'm sure.
Void Singer
Int vSelf = Sing(void);
Join date: 24 Sep 2005
Posts: 6,973
07-05-2009 19:00
maybe some kind character handling difference between MONO and LSO? I didn't see anything obvious skimming it.
_____________________
|
| . "Cat-Like Typing Detected"
| . This post may contain errors in logic, spelling, and
| . grammar known to the SL populace to cause confusion
|
| - Please Use PHP tags when posting scripts/code, Thanks.
| - Can't See PHP or URL Tags Correctly? Check Out This Link...
| -
Basement Desade
Registered User
Join date: 14 Jul 2006
Posts: 91
07-06-2009 00:10
Ah, Void, I was rather hoping you would jump in. :)


From: Void Singer
maybe some kind character handling difference between MONO and LSO? I didn't see anything obvious skimming it.


If you are referring to the reset vs. save thing, perhaps. You'd certainly know better than I.

If you are referring to my comment about getting it to do something, what I meant was that it seems to do what is intended, as far as the correct button names popping up, but I don't really understand what it is doing as configured. Like what does "Start Vote" mean? (I vote for more documentation) :)

This would be neither here nor there for me, If I could figure out how to change the button names in the sub menus, and/or where some of those names come from. In most of the sub menus they are simply the numerals 1-9. I can change the main menu names via the notecard, but I don't understand what "OPTION 1" and so on are there for, except perhaps as placeholders? But, when I change them, there doesn't seem to be any effect on the dialog menu.

I have been experimenting by changing various aspects of the notecard to see if the expected changes occurred in the dialog menu, and mostly they have, except for these.
Then, of course, once I can change the OPTION placeholders(?) correctly in order to change the menu, I then need to understand how to translate that into an action, i.e. "If I press the button labeled 'Blue,' turn side 0 blue." (I know the syntax to change side 0 blue in a "normal" script, and even a in dialog script with no nested menus, just not where to put it in this one.)

And one more question. Do nested dialog menu scripts REQUIRE notecards. or is this just for the convenience of the user?

Just as an example, here's the sort of thing I'm used to dealing with:

CODE

list buttons = ["Blue", "Green", "Done"];
string msg = "Choose Color";

key Owner;
integer channel_dialog;
integer listen_id;
default{
state_entry() {
Owner = llGetOwner();
channel_dialog = ( -1 * (integer)("0x"+llGetSubString((string)llGetKey(),-5,-1)) );
}

touch_start(integer total_number) {
if(llDetectedKey(0)==Owner){
llDialog(Owner, msg, buttons, channel_dialog);
listen_id = llListen( channel_dialog, "", Owner, "");
}
}


listen(integer channel, string name, key id, string choice) {
if (choice == "-") {
llDialog(Owner, msg,buttons, channel_dialog);
}
else if (choice == "Blue") {
llSetColor(< 0,0,1>,0);
llListenRemove(listen_id);
}
else if (choice == "Green") {
llSetColor(< 0,1,0>,0);
llListenRemove(listen_id);
}
else if (choice == "Done") {
llSay(0,"Script deleting.");
llRemoveInventory(llGetScriptName());
llMessageLinked(LINK_SET, 0, "Done","");
llListenRemove(listen_id);
}
}

timer() {
llListenRemove(listen_id);
llSetTimerEvent(0.0);
}

on_rez(integer start_param)
{
llResetScript();
}
}



This example is, of course, a rather silly, pointless script, at least as configured, BUT...


No notecard, no muss, no fuss. Is there perhaps an easy way to nest sub-menus in this script example?
Void Singer
Int vSelf = Sing(void);
Join date: 24 Sep 2005
Posts: 6,973
07-06-2009 04:45
looks like I need to put some work back into my menu handler... (not this, this ins't mine)

don't have time atm to look into this one, but the trick I used was to name sub menus identical to buttons and compile them as 2 separate lists... one master list with the menu name, followed by it's button count, (and whatever buttons) and another list that just holds the different menu names.... when a response comes back from the button press, I seacrh the master list, if it's a sub menu name, I pop up that sub menu... otherwise act on the data as normal.
_____________________
|
| . "Cat-Like Typing Detected"
| . This post may contain errors in logic, spelling, and
| . grammar known to the SL populace to cause confusion
|
| - Please Use PHP tags when posting scripts/code, Thanks.
| - Can't See PHP or URL Tags Correctly? Check Out This Link...
| -
Innula Zenovka
Registered User
Join date: 20 Jun 2007
Posts: 1,825
07-07-2009 17:14
May I check that I've understood how that works, Void?

When I get a message, I see if it's in my list of sub-menu names. If it is, I then look for it in the master list. When I find it there, I know the next entry is an integer telling me how many buttons there are on that particular sub-menu, and that this integer is followed by the buttons themselves, so I can pull them out and generate the appropriate sub-menu.

Is this the mechanism?
Void Singer
Int vSelf = Sing(void);
Join date: 24 Sep 2005
Posts: 6,973
07-07-2009 17:46
From: Innula Zenovka
May I check that I've understood how that works, Void?

When I get a message, I see if it's in my list of sub-menu names. If it is, I then look for it in the master list. When I find it there, I know the next entry is an integer telling me how many buttons there are on that particular sub-menu, and that this integer is followed by the buttons themselves, so I can pull them out and generate the appropriate sub-menu.

Is this the mechanism?

you got it =)
_____________________
|
| . "Cat-Like Typing Detected"
| . This post may contain errors in logic, spelling, and
| . grammar known to the SL populace to cause confusion
|
| - Please Use PHP tags when posting scripts/code, Thanks.
| - Can't See PHP or URL Tags Correctly? Check Out This Link...
| -