More thorough implementation of the engine
This commit is contained in:
parent
7af5d6d9f7
commit
7b0ad9f60b
1 changed files with 316 additions and 0 deletions
316
Next Engine.lsl
Normal file
316
Next Engine.lsl
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue