/* Copyright Aria's Creations 2024 Dialog Module - Next Engine v1.0.080124.1829 08-2024 INITIAL RELEASE * Finished hooking up listener and timer * Add ability to replace menu buttons (Toggles) 06-2024 INITIAL VERSION * Manage memory more efficiently than all other current systems * Channels for dialogs will always be negative * User-input prompts will use a positive channel and inform the user the channel number for manual longer input * Event signal on link message when requesting only a channel number * Two helper functions allow serializing and deserializing menus from the linkset_data * Menus are not stored in memory, only a pointer to it * Menus consist of a linksetdata json object * In-memory objects consist of channel number, menu name pointing to LSD, ID listening to * ID may not be a user for listen only. In this case the menu name is blank. * Dynamic Menus. * Menu Construction. We as part of the startup process, send a signal to all scripts asking for menu / registration. All subsequent buttons will be treated as possible submenus, a signal will be sent for them recursively to build up the menu hierarchy. */ integer LINK_SIGNAL_GEN_CHANNEL = 0601241; integer LINK_SIGNAL_CHANNEL_BACK = 0601242; integer LINK_SIGNAL_SHOW_MENU = 0601243; integer LINK_SIGNAL_MENU_TIMEOUT = 0601244; integer LINK_SIGNAL_REREGISTER_MENUS= 0601245; integer LINK_SIGNAL_QUERY_MENU = 0602241; integer LINK_SIGNAL_REGISTER_MENU = 0602242; integer LINK_SIGNAL_RESET = 0602243; integer LINK_SIGNAL_MENU_DATA = 0602244; integer LINK_SIGNAL_MENU_BACK = 0801241; integer LINK_SIGNAL_REPLACE_BUTTON = 0804241; string PREVIOUS_MENU = "<--"; string EXIT_MENU = "-exit-"; string NEXT_MENU = "-->"; integer g_iDebugIndent=0; returnMenu(key kID, string sMenu, integer iPage, string sReply) { llMessageLinked(LINK_SET, LINK_SIGNAL_MENU_BACK, llList2Json(JSON_OBJECT, ["menu", sMenu, "page", iPage, "reply", sReply]), kID); } integer DEBUG_ENABLED() { return FALSE; } DEBUG_FUNC(integer iEnter, string sLabel, list lParams) { if(!DEBUG_ENABLED()) return; if(iEnter){ llOwnerSay(MakeIndent() + "ENTER " + sLabel + " [" + llList2CSV(lParams) + "]"); g_iDebugIndent++; } else { g_iDebugIndent --; if(g_iDebugIndent<0)g_iDebugIndent=0; llOwnerSay(MakeIndent() + "LEAVE " + sLabel + " [" + llList2CSV(lParams) + "]"); } } DEBUG_STMT(integer iEnter, string sLabel) { if(!DEBUG_ENABLED()) return; if(iEnter) { llOwnerSay(MakeIndent() + "STMT " + sLabel); g_iDebugIndent ++; }else { g_iDebugIndent--; if(g_iDebugIndent<0)g_iDebugIndent=0; llOwnerSay(MakeIndent() + "RET " + sLabel); } } DEBUG(string sMsg) { if(!DEBUG_ENABLED()) return; llOwnerSay(MakeIndent() + " > " + sMsg); } string MakeIndent() { integer i = 0; string sIndent = ""; for(i = 0;i25){ if(llGetSubString(sID,8,8)=="-" && llGetSubString(sID, 13,13) == "-" && llGetSubString(sID,18,18) == "-" && llGetSubString(sID,23,23)=="-") return TRUE; } return FALSE; } integer IsLikelyAvatarID(key kID) { if(!IsLikelyUUID(kID))return FALSE; // Avatar UUIDs always have the 15th digit set to a 4 if(llGetSubString(kID,8,8) == "-" && llGetSubString(kID,14,14)=="4")return TRUE; return FALSE; } integer IsListOfIDs(list lIDs) { integer i=0; integer end = llGetListLength(lIDs); for(i=0;i 0) return -iRand; else return iRand; } } // This function serializes a named menu to the buffer saveMenu(string sName, list lButtons, list lUtilityButtons, integer iMenuVersion, string sMenuText) { llLinksetDataWrite("menus." + sName, llList2Json(JSON_OBJECT, [ "buttons", llList2Json(JSON_ARRAY, lButtons), "utility", llList2Json(JSON_ARRAY, lUtilityButtons), "version", iMenuVersion, // This flag is used to know if on startup a script needs to overwrite this menu definition, "prompt", sMenuText ])); } updateMenu(string sName, string sJson) { llLinksetDataWrite("menus." + sName, sJson); } // This function deserializes a menu from the buffer string readMenu(string sName) { string sMenu = llLinksetDataRead("menus." + sName); return sMenu; } integer STRIDE = 7; integer STRIDE_ID = 0; integer STRIDE_PATH = 1; integer STRIDE_CHANNEL = 2; integer STRIDE_TEXT = 3; integer STRIDE_PAGE = 4; integer STRIDE_HANDLE = 5; integer STRIDE_TIMER = 6; list g_lListeners = []; integer MENU_TIMER = 30; integer GENERAL_TIMER = 120; startListen(key kAv, integer iChannel, string sMenuName, string sMenuText, integer iTimer) { integer iIndex=llListFindList(g_lListeners, [kAv]); if(iIndex == -1) { integer iListener = llListen(iChannel, "", kAv, ""); g_lListeners += [kAv, sMenuName, iChannel, sMenuText, 0, iListener, llGetUnixTime() + iTimer]; }else { stopListen(kAv); startListen(kAv, iChannel, sMenuName, sMenuText, iTimer); } } stopListen(key kAv) { integer iIndex = llListFindList(g_lListeners, [kAv]); if(iIndex != -1) { g_lListeners = llDeleteSubList(g_lListeners, iIndex, iIndex+STRIDE-1); // Stride is 6 - ID, path, channel, text, page, listen handle } } updatePage(key kID, integer iPage) { integer iIndex = llListFindList(g_lListeners, [kID]); if(iIndex!=-1) { g_lListeners = llListReplaceList(g_lListeners, [iPage], iIndex+STRIDE_PAGE, iIndex+STRIDE_PAGE); } } integer getListenTimer(key kID) { integer iIndex = llListFindList(g_lListeners, [kID]); if(iIndex != -1) { integer iVal = llList2Integer(g_lListeners, iIndex+STRIDE_TIMER); return iVal; }else return -1; } integer getPageNumber(key kID) { integer iIndex = llListFindList(g_lListeners, [kID]); if(iIndex != -1) { return llList2Integer(g_lListeners, iIndex + STRIDE_PAGE); } else return -1; } updateTimer(key kID, integer iNewTimer) { integer iIndex = llListFindList(g_lListeners, [kID]); if(iIndex != -1) { g_lListeners = llListReplaceList(g_lListeners, [llGetUnixTime() + iNewTimer], iIndex + STRIDE_TIMER, iIndex + STRIDE_TIMER); } } string getMenuID(key kID) { integer iIndex = llListFindList(g_lListeners, [kID]); if(iIndex != -1) { return llList2String(g_lListeners, iIndex + STRIDE_PATH); } else return ""; } integer getChannel(key kID) { integer iIndex = llListFindList(g_lListeners, [kID]); if(iIndex!=-1) { return llList2Integer(g_lListeners, iIndex+STRIDE_CHANNEL); } else return -1; } string CreateBlankMenu() { return llList2Json(JSON_OBJECT, [ "prompt", "", "buttons", "[]", "utility", llList2Json(JSON_ARRAY, []), "version", "1" ]); } string AddMenuButton(string sJson, string sButton) { list lButtons = llJson2List(llJsonGetValue(sJson, ["buttons"])); lButtons += [sButton]; return llJsonSetValue(sJson, ["buttons"], llList2Json(JSON_ARRAY, lButtons)); } integer jsonValueExists(string sJson, list lElems) { if(llJsonValueType(sJson, lElems) == JSON_INVALID) return FALSE; else return TRUE; } integer hasPreviousPage(integer page){ if(page == 1) { return FALSE; }else return TRUE; } integer hasNextPage(integer page, integer maxPages) { if(page == maxPages) return FALSE; else return TRUE; } list getNavigatorButtons(integer page, integer max) { if(hasPreviousPage(page)) { if(hasNextPage(page,max)) { return [PREVIOUS_MENU, EXIT_MENU, NEXT_MENU]; }else return [PREVIOUS_MENU, EXIT_MENU, " "]; }else { if(hasNextPage(page,max)) { return [" ", EXIT_MENU, NEXT_MENU]; }else return [" ", EXIT_MENU, " "]; } } // This function takes the json menu as a parameter, calculates the maximum number of pages from total buttons 12 - utility - page buttons if applicable integer calcMaxPages(string sMenu) { // Retrieve buttons and utility lists from JSON list lButtons = llJson2List(llJsonGetValue(sMenu, ["buttons"])); list lUtility = llJson2List(llJsonGetValue(sMenu, ["utility"])); // Combine buttons and utility lists list lFinal = lButtons + lUtility; // Calculate the total number of buttons integer totalButtons = llGetListLength(lFinal); // Check if the total buttons exceed the limit for a single page if (totalButtons > 12) { // Define constants integer navigation = 3; // 3 navigation buttons (previous, next, and exit) integer utility = llGetListLength(lUtility); // Number of utility buttons integer buttons = llGetListLength(lButtons); // Number of actual buttons // Calculate available space for actual buttons on each page integer availableSpacePerPage = 12 - navigation - utility; // Calculate the total number of pages needed integer maxPages = (integer)llCeil((float)buttons / (float)availableSpacePerPage); return maxPages; } else { // If total buttons are 12 or less, only one page is needed return 1; } } string getPage(string sMenu, integer iPage, integer iCheckUUID) { DEBUG_FUNC(TRUE, "getPage", [sMenu, iPage, iCheckUUID]); // Retrieve buttons and utility lists from JSON list lButtons = llJson2List(llJsonGetValue(sMenu, ["buttons"])); list lUtility = llJson2List(llJsonGetValue(sMenu, ["utility"])); // Define constants integer navigation = 3; // 3 navigation buttons (previous, next, and page indicator) integer utility = llGetListLength(lUtility); // Number of utility buttons integer buttonsPerPage = 12 - navigation - utility; // Available space for actual buttons on each page // Calculate the total number of pages using the existing function integer totalPages = calcMaxPages(sMenu); // Ensure iPage is within valid range if (iPage < 1) iPage = 1; if (iPage > totalPages) iPage = totalPages; // Calculate start and end indices for the buttons on the requested page integer startIndex = (iPage - 1) * buttonsPerPage; integer endIndex = startIndex + buttonsPerPage - 1; // Get the actual buttons to be displayed on this page list lPageButtons = []; // Add utility buttons to the page lPageButtons += lUtility; // Add the buttons for the specified page integer i; for (i = startIndex; i <= endIndex && i < llGetListLength(lButtons); ++i) { lPageButtons += llList2String(lButtons, i); } // Add navigation buttons lPageButtons = getNavigatorButtons(iPage, totalPages) + lPageButtons; // Loop over the buttons, check if UUID, replace with a number instead if UUID string sExtraText = " "; if(!iCheckUUID) jump returnMenu; i =0; integer end = llGetListLength(lPageButtons); integer idNum = 0; for(i=0;i iTimer) { stopListen(kID); llRegionSayTo(kID, 0, "Listener Timed Out"); return; } } } changed(integer t) { if(t & CHANGED_INVENTORY) { llSleep(2); llResetScript(); } } listen(integer c,string n,key i,string m) { // Get the menu path DEBUG_FUNC(TRUE, "listen", [i,m]); string sMenuID = getMenuID(i); // Get the current page number integer iPage = getPageNumber(i); DEBUG("Get current page number for menu " + sMenuID + ": " + (string)iPage); string jsMenu = ""; if(sMenuID != "") jsMenu = readMenu(sMenuID); DEBUG("Menu Json: " + jsMenu); integer iMaxPages = 0; if(sMenuID != "") iMaxPages = calcMaxPages(jsMenu); DEBUG("Get max number of pages: " + (string)iMaxPages); if(m == PREVIOUS_MENU) { DEBUG_STMT(TRUE, "Previous Menu"); // Update the time remaining updateTimer(i, MENU_TIMER); iPage--; if(iPage < 0) iPage=0; string sPage = getPage(jsMenu, iPage, TRUE); list lPageButtons = llParseString2List(llJsonGetValue(sPage, ["buttons"]), ["~!~"], []); updatePage(i, iPage); string sAppend = llJsonGetValue(sPage, ["text"]); if(iMaxPages > 1) sAppend += "\n\nPage " + (string)iPage + "/" + (string)(iMaxPages-1); llDialog(i, llJsonGetValue(jsMenu, ["prompt"]) + sAppend, lPageButtons, getChannel(i)); DEBUG_STMT(FALSE, "Previous Menu"); } else if(m == NEXT_MENU) { DEBUG_STMT(TRUE, "Next Menu"); // Update the time remaining updateTimer(i, MENU_TIMER); iPage++; if(iPage < iMaxPages-1) iPage=iMaxPages-1; string sPage = getPage(jsMenu, iPage, TRUE); list lPageButtons = llParseString2List(llJsonGetValue(sPage, ["buttons"]), ["~!~"], []); updatePage(i, iPage); string sAppend = llJsonGetValue(sPage, ["text"]); if(iMaxPages > 1) sAppend += "\n\nPage " + (string)iPage + "/" + (string)(iMaxPages-1); llDialog(i, llJsonGetValue(jsMenu, ["prompt"]) + sAppend, lPageButtons, getChannel(i)); DEBUG_STMT(FALSE, "Next Menu"); } else if(m == " ") { DEBUG_STMT(TRUE, "Empty Button"); // Update the time remaining updateTimer(i, MENU_TIMER); string sPage = getPage(jsMenu, iPage, TRUE); list lPageButtons = llParseString2List(llJsonGetValue(sPage, ["buttons"]), ["~!~"], []); updatePage(i, iPage); string sAppend = llJsonGetValue(sPage, ["text"]); if(iMaxPages > 1) sAppend += "\n\nPage " + (string)iPage + "/" + (string)(iMaxPages-1); llDialog(i, llJsonGetValue(jsMenu, ["prompt"]) + sAppend, lPageButtons, getChannel(i)); DEBUG_STMT(FALSE, "Empty Button"); } else if(m == EXIT_MENU) { DEBUG_STMT(TRUE, "Exit Menu"); stopListen(i); DEBUG_STMT(FALSE, "Exit Menu"); } else { DEBUG_STMT(TRUE, "Process Response"); // Stop listening, and send a signal string sPage = ""; if(jsMenu != "") sPage = getPage(jsMenu, iPage, FALSE); DEBUG("Parse Buttons"); // Check Message, if message is found in list, it isn't UUID. // If message is not found, it is likely uuid list lButtons = llParseString2List(llJsonGetValue(sPage, ["buttons"]), ["~!~"], []); integer iIndex = llListFindList(lButtons, [m]); DEBUG("Buttons : " + llDumpList2String(lButtons, " ~ ")); if(iIndex==-1 && sMenuID != "") { // Loop over to find the index integer iIDNum = (integer)m; integer x = 0; integer xe = llGetListLength(lButtons); integer iID = 0; for(x=0;x1) sAppend += "\n\nPage " + (string)iPage+"/" + (string)(maxPages-1); //llOwnerSay("DEBUG\n" + llJsonGetValue(sMenu, ["prompt"]) + sAppend + "\n\n" + llJsonGetValue(sPage, ["buttons"])); llDialog(i, llJsonGetValue(jsMenu, ["prompt"])+sAppend, lPageButtons, getChannel(i)); } else if(n == LINK_SIGNAL_REGISTER_MENU) { string sMenuJson = llLinksetDataRead("menus." + (string)i); if(sMenuJson == "" ) { // No current menu sMenuJson = CreateBlankMenu(); sMenuJson = AddMenuButton(sMenuJson, m); }else sMenuJson = AddMenuButton(sMenuJson, m); llLinksetDataWrite("menus."+(string)i, sMenuJson); llSleep(0.5); llMessageLinked(LINK_SET, LINK_SIGNAL_QUERY_MENU, (string)i + "/" + m, ""); } else if(n == LINK_SIGNAL_RESET) { llResetScript(); } else if(n == LINK_SIGNAL_MENU_DATA) { // This signal is used to replace metadata, like the menu text, or utility buttons. // Based on what values are set in the json object, we will replace those elements in the actual menu string sJson = readMenu(i); if(jsonValueExists(m, ["utility"])) { sJson = llJsonSetValue(sJson, ["utility"], llJsonGetValue(m, ["utility"])); } if(jsonValueExists(m, ["prompt"])) { sJson = llJsonSetValue(sJson, ["prompt"], llJsonGetValue(m, ["prompt"])); } llLinksetDataWrite("menus." + (string)i, sJson); } else if(n == LINK_SIGNAL_REPLACE_BUTTON) { string jsMenu = readMenu(llJsonGetValue(m, ["menu"])); list lButtons = llJson2List(llJsonGetValue(jsMenu, ["buttons"])); integer iIndex = llListFindList(lButtons, [llJsonGetValue(m,["btn"])]); if(iIndex != -1) { lButtons = llListReplaceList(lButtons, [llJsonGetValue(m, ["newBtn"])], iIndex, iIndex); jsMenu = llJsonSetValue(jsMenu, ["buttons"], llList2Json(JSON_ARRAY, lButtons)); updateMenu(llJsonGetValue(m,["menu"]), jsMenu); } } } linkset_data(integer iAction, string sKey, string sValue) { if(iAction == LINKSETDATA_RESET) { llMessageLinked(LINK_SET, LINK_SIGNAL_REREGISTER_MENUS, "", ""); llResetScript(); } } }