Dialog Control v1.8

From Second Life Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
// ********** SIMPLE DIALOG MODULE ********** //
// By Nargus Asturias
// Version 1.86
//
// Support only one dialog at a time. DO NOT request multiple dialog at once!
// Use of provided functions are recommented. Instruction here are for hardcore programmers only!
//
// Request: Send MessageLinked to the script. There are 3 dialog modes:
//      lnkDialog               : Normal dialog with message and buttons
//      lnkDialogNumericAdjust  : A dialog with buttons can be used to adjust numeric value
//      lnkDialogTextBox        : A dialog with a text input field
//      lnkDialogNotify         : Just a simple notification dialog. No return value and no buttons.
//
// Send MessageLinked with code lnkDialogReshow to force active dialog to reappear.
// Send MessageLinked with code lnkDialogCancel to force cancel active dialog
//
// If a lnkDialog is requested with more than 12 buttons, multi-pages dialog is used to show the buttons.
//
// [ FOR lnkDialog ]
// MessageLinked Format:
//      String part: List dumped into string, each entry seperated by '||'
//          Field 1:    Dialog message (512 bytes maximum)
//          Field 2:    Time-out data (integer)
//          Field 3-4:  Button#1 and return value pair
//          Field 5-6:  Button#2 and return value pair
//          And go on...
//      Key part: Key of AV who attend this dialog
//
// Response: MessageLinked to the prim that requested dialog (but no where else)
//      num == lnkDialogResponse:   AV click on a button. The buttons value returned as a string
//      num == lnkDialogTimeOut:    Dialog timeout.
//
// [ FOR lnkDialogTextbox ]
// MessageLinked Format:
//      String part: List dumped into string, each entry seperated by '||'
//          Field 1:    Dialog message (512 bytes maximum)
//          Field 2:    Time-out data (integer)
//          Field 3:    Return string prefix. If empty, only user's input will be returned.
//
// [ FOR lnkDialogNumericAdjust ]
// MessageLinked Format:
//      String part: List dumped into string, each entry seperated by '||'
//          Field 1:    Dialog message (512 bytes maximum)
//                          Put {VALUE} where you want the current value to be displayed)
//          Field 2:    Time-out data (integer)
//          Field 3:    Most significant value (ie. 100, for +/-100)
//          Field 4:    String-casted numeric value to be adjusted
//          Field 5:    2nd most significant value (ie. 10, for +/-10)
//          Field 6:    Use '1' to enable "+/-" button, '0' otherwise.
//          Field 7:    3nd significant value (ie. 1, for +/-1)
//          Field 8:    Use '1' for integer, or '0' for float
//          Field 9:    Least significant value (ie. 0.1, for +/-0.1)
//          Field 10:   Reserved. Not used.
//      Key part: Key of AV who attend this dialog
//
// Response: MessageLinked to the prim that requested dialog (but no where else)
//      num == lnkDialogResponse:   OK or Cancel button is clicked. The final value returned as string.
//      num == lnkDialogTimeOut:    Dialog timeout.
//
// ******************************************* //

// Constants
integer lnkDialog = 14001;
integer lnkDialogNumericAdjust = 14005;
integer lnkDialogNotify = 14004;
integer lnkDialogTextBox = 14007;

integer lnkDialogReshow = 14011;
integer lnkDialogCancel = 14012;

integer lnkDialogResponse = 14002;      // A button is hit, or OK is hit for lnkDialogNumericAdjust
integer lnkDialogCanceled = 14006;      // Cancel is hit for lnkDialogNumericAdjust
integer lnkDialogTimeOut = 14003;       // No button is hit, or Ignore is hit

integer lnkMenuClear = 15001;
integer lnkMenuAdd = 15002;
integer lnkMenuShow = 15003;
integer lnkMenuNotFound = 15010;
integer lnkMenuSetSound = 15021;

string seperator = "||";

// Menus variables
list menuNames = [];        // List of names of all menus
list menus = [];            // List of packed menus command, in order of menuNames

integer lastMenuIndex = 0;  // Latest called menu's index

string menuSound;           // Sound to me played when a menu is shown
float menuSoundVol = 1;

// Dialog variables
integer dialogChannel;      // Channel number used to spawn this dialog
string message;             // Message to be shown with the dialog
integer timerOut;           // Dialog time-out
key keyId;                  // Key of user who attending this dialog
integer requestedNum;       // Link-number of the requested prim
list buttons;               // List of dialog buttons
list returns;               // List of results from dialog buttons

float numericValue;
integer useInteger;

// Other variables
integer buttonsCount;
integer startItemNo;
integer listenId;

string redirectState;

integer responseInt = -1;
string responseStr;
key responseKey;


// ********** String Functions **********
string replaceString(string pattern, string replace, string source){
    integer index = llSubStringIndex(source, pattern);
    if(index < 0) return source;
    
    source = llDeleteSubString(source, index, (index + llStringLength(pattern) - 1));
    return llInsertString(source, index, replace);
}

// ********** Dialog Functions **********
// Function: createDialog
// Create dialog with given message and buttons, direct to user with give id
integer createDialog(key id, string message, list buttons){
    integer channel = -((integer)llFrand(8388608))*(255) - (integer)llFrand(8388608) - 11;

    llListenRemove(listenId);
    listenId = llListen(channel, "", keyId, "");
    if(buttonsCount > 0)
        llDialog(keyId, message, buttons, channel);
    else llTextBox(keyId, message, channel);

    return channel;
}

// Function: createMultiDialog
// Create dialog with multiple pages. Each page has Back, Next, and a Close button. Otherwise same functionality as createDialog() function.
integer createMultiDialog(key id, string message, list buttons, integer _startItemNo){
    integer channel = -llRound(llFrand( llFabs(llSin(llGetGMTclock())) * 1000000 )) - 11;

    if(_startItemNo < 0) _startItemNo = 0;
    if(_startItemNo >= buttonsCount - 1) _startItemNo -= 9;
    startItemNo = _startItemNo;
    
    integer vButtonsCount = buttonsCount - 2;
    
    // Generate list of buttons to be shown
    string closeButton = llList2String(buttons, buttonsCount - 1);
    
    integer stopItemNo = startItemNo + 8;
    if(stopItemNo >= buttonsCount - 1) stopItemNo = vButtonsCount;

    list thisButtons = llList2List(buttons, startItemNo, stopItemNo);

    // Generate dialog navigation buttons
    integer i = stopItemNo - startItemNo + 1;
    i = i % 3;
    if(i > 0) while(i < 3){
        thisButtons += [" "];
        ++i;
    }
    
    if(startItemNo > 0)
        thisButtons += ["<< BACK"];
    else thisButtons += [" "];
    
    thisButtons += [closeButton];
    
    if(stopItemNo < vButtonsCount)
        thisButtons += ["NEXT >>"];
    else thisButtons += [" "];
    
    // Append page number to the message
    integer pageNumber = (integer)(stopItemNo / 9) + 1;
    integer pagesCount = llCeil(vButtonsCount / 9.0);
    string vMessage = "PAGE: " + (string)pageNumber + " of " + (string)pagesCount + "\n" +
        message;

    // Display dialog
    llListenRemove(listenId);
    listenId = llListen(channel, "", keyId, "");
    llDialog(keyId, vMessage, thisButtons, channel);

    return channel;
}

// Function: generateNumericAdjustButtons
// Generate numeric adjustment dialog which adjustment values are in given list.
// If useNegative is TRUE, "+/-" button will be available.
list generateNumericAdjustButtons(list adjustValues, integer useNegative){
    list dialogControlButtons;
    list positiveButtons;
    list negativeButtons;
    list additionButtons;

    dialogControlButtons = ["OK", "Cancel"];

    // Config adjustment buttons
    integer count = llGetListLength(adjustValues);
    integer index;
    for(index = 0; (index < count) && (index < 3); index++){
        string sValue = llList2String(adjustValues, index);

        if((float)sValue != 0){
            positiveButtons += ["+" + sValue];
            negativeButtons += ["-" + sValue];
        }
    }

    // Check positive/negative button
    if(useNegative)
        additionButtons = ["+/-"];
    else additionButtons = [];

    // If there is fourth adjustment button
    if(count > 3){
        if(llGetListLength(additionButtons) == 0) additionButtons = [" "];

        string sValue = llList2String(adjustValues, index);
        additionButtons += ["+" + sValue, "-" + sValue];
    }else if(additionButtons != []) additionButtons += [" ", " "];
    
    // Return list dialog buttons
    return additionButtons + negativeButtons + positiveButtons + dialogControlButtons;
}

setResponse(integer int, string str, key id){
    responseInt = int;
    responseStr = str;
    responseKey = id;
}

checkDialogRequest(integer sender_num, integer num, string str, key id){
    // Common dialog requests
    if((num == lnkDialogNotify) || (num == lnkDialogNumericAdjust) || (num == lnkDialog)){
        list data = llParseStringKeepNulls(str, [seperator], []);
    
        message = llList2String(data, 0);
        timerOut = llList2Integer(data, 1);
        keyId = id;
        requestedNum = sender_num;
        buttons = [];
        returns = [];
    
        if(message == "") message = " ";
        if(timerOut > 7200) timerOut = 7200;
    
        // Generate buttons list
        integer i=2;
        integer count = llGetListLength(data);
        if(count > 2){
            buttonsCount = 0;
            for(; i<count;){
                buttons += [llList2String(data, i++)];
                returns += [llList2String(data, i++)];
                ++buttonsCount;
            }
        }else{
            buttons = ["OK"];
            returns = [];
            buttonsCount = 1;
        }
    
        // Determine the type of dialog
        if(num == lnkDialogNotify){
            dialogChannel = -((integer)llFrand(8388608))*(255) - (integer)llFrand(8388608) - 11;
            llDialog(keyId, message, buttons, dialogChannel);
            return;
        }else if(num == lnkDialogNumericAdjust)
            redirectState = "NumericAdjustDialog";
        else if(num == lnkDialog){
            if(buttonsCount > 12)
                redirectState = "MultiDialog";
            else redirectState = "Dialog";
        }
        
        if(TRUE) state StartDialog;
    
    // Text box request
    }else if(num == lnkDialogTextBox){
        list data = llParseStringKeepNulls(str, [seperator], []);

        message = llList2String(data, 0);
        timerOut = llList2Integer(data, 1);
        keyId = id;
        requestedNum = sender_num;
        buttons = [];
        returns = [llList2String(data, 2)];
        buttonsCount = 0;

        if(timerOut > 7200) timerOut = 7200;
    
        redirectState = "Dialog";
        if(TRUE) state StartDialog;
    
    // Otherwise; Check for menu requests
    }else checkMenuRequest(sender_num, num, str, id);
}

// ********** Menu Functions **********
clearMenusList(){
    menuNames = [];
    menus = [];

    lastMenuIndex = 0;
}

addMenu(string name, string message, list buttons, list returns){
    // Reduced menu request time by packing request commands
    string packed_message = message + seperator + "0";
    
    integer i;
    integer count = llGetListLength(buttons);
    for(i=0; i<count; i++) packed_message += seperator + llList2String(buttons, i) + seperator + llList2String(returns, i);

    // Add menu to the menus list
    integer index = llListFindList(menuNames, [name]);
    if(index >= 0)
        menus = llListReplaceList(menus, [packed_message], index, index);
    else{
        menuNames += [name];
        menus += [packed_message];
    }
}

integer showMenu(string name, key id){
    if(llGetListLength(menuNames) <= 0) return FALSE;
   
    integer index;
    if(name != ""){
        index = llListFindList(menuNames, [name]);
        if(index < 0) return FALSE;
    }else index = lastMenuIndex;
    
    lastMenuIndex = index;
    
    // Load menu command and execute
    string packed_message = llList2String(menus, index);

    if(menuSound != "") llPlaySound(menuSound, menuSoundVol);
    llMessageLinked(LINK_THIS, lnkDialog, packed_message, id);
    return TRUE;
}

checkMenuRequest(integer sender_num, integer num, string str, key id){
    // Menu response commands
    if(num == lnkDialogResponse){
        if(llGetSubString(str, 0, 4) == "MENU_"){
            str = llDeleteSubString(str, 0, 4);
            showMenu(str, id);
        }
    }
        
    // Menu management commands
    else if(num == lnkMenuClear)
        clearMenusList();
    else if(num == lnkMenuAdd){
        list data = llParseString2List(str, [seperator], []);

        string message = llList2String(data, 0);
        list buttons = [];
        list returns = [];

        integer i;
        integer count = llGetListLength(data);
        for(i=2; i<count;){
            buttons += [llList2String(data, i++)];
            returns += [llList2String(data, i++)];
        }

        addMenu((string)id, message, buttons, returns);

    }else if(num == lnkMenuShow){
        if(!showMenu(str, id)) llMessageLinked(sender_num, lnkMenuNotFound, str, NULL_KEY);

    }else if(num == lnkMenuSetSound){
        menuSound = str;
        menuSoundVol = (float)((string)id);
    }
}

// ********** States **********
default{
    state_entry(){
        if(responseInt > 0) llMessageLinked(requestedNum, responseInt, responseStr, responseKey);
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        checkDialogRequest(sender_num, num, str, id);
    }
}

state StartDialog{
    state_entry(){
        if(redirectState == "Dialog")                   state Dialog;
        else if(redirectState == "MultiDialog")         state MultiDialog;
        else if(redirectState == "NumericAdjustDialog") state NumericAdjustDialog;
        else state default;
    }
}

state Dialog{
    state_entry(){
        responseInt = -1;
        dialogChannel = createDialog(keyId, message, buttons);
        llSetTimerEvent(timerOut);
    }
    
    state_exit(){
        llSetTimerEvent(0);
    }
    
    on_rez(integer start_param){
        state default;
    }
    
    timer(){
        setResponse(lnkDialogTimeOut, "", keyId);
        state default;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == lnkDialogReshow){
            dialogChannel = createDialog(keyId, message, buttons);
            llSetTimerEvent(timerOut);
        }else if(num == lnkDialogCancel) state default;

        else checkDialogRequest(sender_num, num, str, id);
    }

    listen(integer channel, string name, key id, string msg){
        if((channel != dialogChannel) || (id != keyId)) return;
        
        if(buttonsCount > 0){
            channel = llListFindList(buttons, [msg]);
            msg = llList2String(returns, channel);
        }else msg = llList2String(returns, 0) + msg;
        setResponse(lnkDialogResponse, msg, keyId);
        state default;
    }
}

state MultiDialog {
    state_entry(){
        responseInt = -1;
        startItemNo = 0;
        dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
        llSetTimerEvent(timerOut);
    }
    
    state_exit(){
        llSetTimerEvent(0);
    }
    
    on_rez(integer start_param){
        state default;
    }
    
    timer(){
        setResponse(lnkDialogTimeOut, "", keyId);
        state default;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == lnkDialogReshow){
            dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
            llSetTimerEvent(timerOut);
        }else if(num == lnkDialogCancel) state default;

        else checkDialogRequest(sender_num, num, str, id);
    }

    listen(integer channel, string name, key id, string msg){
        if((channel != dialogChannel) || (id != keyId)) return;

        // Dialog control buttons
        if(msg == "<< BACK"){
            dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo - 9);
            llSetTimerEvent(timerOut);
        }else if(msg == "NEXT >>"){
            dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo + 9);
            llSetTimerEvent(timerOut);
        }else if(msg == " "){
            dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
            llSetTimerEvent(timerOut);
        
        // Response buttons
        }else{
            integer index = llListFindList(buttons, [msg]);
            setResponse(lnkDialogResponse, llList2String(returns, index), keyId);
            state default;
        }
    }
}

state NumericAdjustDialog {
    state_entry(){
        responseInt = -1;

        numericValue = llList2Float(returns, 0);
        useInteger = llList2Integer(returns, 2);
        buttons = generateNumericAdjustButtons(buttons, llList2Integer(returns, 1));
        
        string vMessage;
        if(useInteger)
            vMessage = replaceString("{VALUE}", (string)((integer)numericValue), message);
        else vMessage = replaceString("{VALUE}", (string)numericValue, message);
        
        dialogChannel = createDialog(keyId, vMessage, buttons);
        llSetTimerEvent(timerOut);
    }
    
    state_exit(){
        llSetTimerEvent(0);
    }
    
    on_rez(integer start_param){
        state default;
    }
    
    timer(){
        setResponse(lnkDialogTimeOut, "", keyId);
        state default;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == lnkDialogReshow){
            dialogChannel = createDialog(keyId, message, buttons);
            llSetTimerEvent(timerOut);
        }else if(num == lnkDialogCancel) state default;

        else checkDialogRequest(sender_num, num, str, id);
    }

    listen(integer channel, string name, key id, string msg){
        if((channel != dialogChannel) || (id != keyId)) return;
        
        // Dialog control button is hit
        if(msg == "OK"){
            setResponse(lnkDialogResponse, (string)numericValue, keyId);
            state default;
        }else if(msg == "Cancel"){
            setResponse(lnkDialogCanceled, (string)numericValue, keyId);
            llMessageLinked(requestedNum, lnkDialogCanceled, (string)numericValue, keyId);
            state default;
        
        // Value adjustment button is hit
        }else if(msg == "+/-")
            numericValue = -numericValue;
        else if(llSubStringIndex(msg, "+") == 0)
            numericValue += (float)llDeleteSubString(msg, 0, 0);
        else if(llSubStringIndex(msg, "-") == 0)
            numericValue -= (float)llDeleteSubString(msg, 0, 0);
        
        // Spawn another dialog if no OK nor Cancel is hit
        string vMessage;
        if(useInteger)
            vMessage = replaceString("{VALUE}", (string)((integer)numericValue), message);
        else vMessage = replaceString("{VALUE}", (string)numericValue, message);
        dialogChannel = createDialog(keyId, vMessage, buttons);
        llSetTimerEvent(timerOut);
   }
}