More thorough implementation of the engine

This commit is contained in:
zontreck 2024-06-02 18:57:10 -07:00
parent 7af5d6d9f7
commit 7b0ad9f60b

316
Next Engine.lsl Normal file
View file

@ -0,0 +1,316 @@
/*
Copyright Aria's Creations 2024
Dialog Module - Next Engine
v1.0.060124.1255
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;
string PREVIOUS_MENU = "<--";
string EXIT_MENU = "-exit-";
string NEXT_MENU = "-->";
integer generateChannel(integer iPositive) {
integer iRand = llRound(llFrand(0xFFFF));
if(iPositive) {
return llAbs(iRand);
}else
{
if(iRand > 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
list readMenu(string sName) {
list lMenu = [];
string sMenu = llLinksetDataRead("menus." + sName);
list lButtons = llJson2List(llJsonGetValue(sMenu, ["buttons"]));
list lUtility = llJson2List(llJsonGetValue(sMenu, ["utility"]));
integer iVer = (integer)llJsonGetValue(sMenu, ["version"]);
string sPrompt = llJsonGetValue(sMenu, ["prompt"]);
lMenu += [llGetListLength(lButtons)] + lButtons;
lMenu += [llGetListLength(lUtility)] + lUtility;
lMenu += [iVer];
lMenu += [sPrompt];
return lMenu;
}
list g_lListeners = [];
startListen(key kAv, integer iChannel, string sMenuName, string sMenuText) {
integer iIndex=llListFindList(g_lListeners, [kAv]);
if(iIndex == -1) {
integer iListener = llListen(iChannel, "", kAv, "");
g_lListeners += [kAv, sMenuName, iChannel, sMenuText, 0];
}else {
stopListen(kAv);
startListen(kAv, iChannel, sMenuName);
}
}
stopListen(key kAv) {
integer iIndex = llListFindList(g_lListeners, [kAv]);
if(iIndex != -1) {
g_lListeners = llDeleteSubList(g_lListeners, iIndex, iIndex+4); // Stride is 5 - ID, path, channel, text, page
}
}
updatePage(key kID, integer iPage) {
integer iIndex = llListFindList(g_lListeners, [kID]);
if(iIndex!=-1) {
g_lListeners = llListReplaceList(g_lListeners, [iPage], iIndex+4, iIndex+4);
}
}
integer getChannel(key kID) {
integer iIndex = llListFindList(g_lListeners, [kID]);
if(iIndex!=-1) {
return llList2Integer(g_lListeners, iIndex+2);
}
}
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;
}
}
list getPage(string sMenu, integer iPage) {
// 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 if necessary using the existing function
if (totalPages > 1) {
list navigatorButtons = getNavigatorButtons(iPage, totalPages);
lPageButtons += navigatorButtons;
}
return lPageButtons;
}
default
{
state_entry() {
saveMenu("menus.root", [], [], 1, "Basic and initialized root menu");
llMessageLinked(LINK_SET, LINK_SIGNAL_QUERY_MENU, "root", "");
}
timer() {
integer iTimerActive=0;
if(!iTimerActive){
llSetTimerEvent(0);
}
}
listen(integer c,string n,key i,string m) {
}
link_message(integer s,integer n,string m,key i) {
if(n == LINK_SIGNAL_GEN_CHANNEL) {
integer iChan = generateChannel((integer)m);
startListen(i, iChan, "", "");
llMessageLinked(LINK_SET, LINK_SIGNAL_CHANNEL_BACK, (string)iChan, "");
} else if(n == LINK_SIGNAL_SHOW_MENU) {
string sMenu = readMenu(llJsonGetValue(m,["menu"]));
startListen(i, iChan, m, llJsonGetValue(sMenu, ["prompt"]));
// Show the dialog window now after constructing pages if needed.
integer maxPages = calcMaxPages(sMenu);
// Now show the dialog for page 1
integer iPage = 1;
if(jsonValueExists(m, ["page"])) iPage = (integer)llJsonGetValue(m,["page"]);
list lPageButtons = getPage(sMenu, iPage);
updatePage(i, iPage);
string sAppend = "";
integer maxPages = calcMaxPages(sMenu);
if(maxPages>1) sAppend = "\n\nPage " + (string)iPage+"/" + (string)maxPages;
llDialog(i, llJsonGetValue(sMenu, ["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.1);
llMessageLinked(LINK_SET, LINK_SIGNAL_QUERY_MENU, 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." + i, sJson);
}
}
linkset_data(integer iAction, string sKey, string sValue) {
if(iAction == LINKSETDATA_RESET) {
llMessageLinked(LINK_SET, LINK_SIGNAL_REREGISTER_MENUS, "", "");
llResetScript();
}
}
}