Jump to content

How to make script wait for user input?


Jennifer Boyle
 Share

You are about to reply to a thread that has been inactive for 437 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

I am a rank lsl beginner. I have just made my first useful script. To get it working, I did things like hard code keys, etc., that make it non-generalizable. Now I want to revise it to make usable by anyone anywhere on the grid. I already was using llTextBox to get user input. I added llDialog. I have spent all afternoon trying to figure out how to get the second one to wait until the user has responded to the first before displaying its prompt. I have read a lot of material in the wiki, watched YouTube videos, asked ChatGPT, looked at many scripts online, and done a lot of trial-and-error, and I can't figure out how to do it. 

The script actually functions as intended. I can input appropriate text into the text box, slick submit, and then click on a button in the dialog, and the result is as desired, but having them both on the screen looks inelegant.

How can I use both a text box and a dialog for input in the same script and not have the second prompt appear before the first has disappeared?

Thanks for any help and my apologies if this question is so elementary as to be inappropriate for the forum.

 

Link to comment
Share on other sites

Normally each new dialog box or textbox should replace whatever was on the screen before, and normally clicking on the Submit button in either one should close it.  We'd have to see your script to know how its logic works and how you've managed to get both open at once.

Link to comment
Share on other sites

Unless I'm completely misunderstanding your question, I think the answer is to initiate the dialog after the data from the text box has been received.

integer text_box_channel = 1;
integer dialog_channel = 2;

state_entry ()
{
    llListen (text_box_channel, "", "", "");
    llListen (dialog_channel, "", "", "");
}

touch_start (integer count)
{
    llTextBox (llDetectedKey (0), "text", text_box_channel);
}

listen (integer channel, string name, key id, string message) //the id parameter is that of the avatar using the text box or dialog
{
    if (channel == text_box_channel)
    {
        //do text box stuff
        llDialog (id, "test", ["button1", ...], dialog_channel); //use the id parameter to target the dialog at the avatar that used the text box
    }
    else if (channel == dialog_channel)
    {
        //do dialog stuff
    }
}

Edited by KT Kingsley
Forgot to create the listeners
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

3 hours ago, Jennifer Boyle said:

The script actually functions as intended. I can input appropriate text into the text box, slick submit, and then click on a button in the dialog, and the result is as desired, but having them both on the screen looks inelegant.

How can I use both a text box and a dialog for input in the same script and not have the second prompt appear before the first has disappeared?

Instead of calling llTextBox and llDialog during the same event, you should only call one of them and add a condition for the second one.

KT's example does exactly that. llTextBox is called first, and then llDialog is called only if the previous/current input came from the text box. Once the second input is processed, nothing is opened again. (You could open a third dialog or text box there if you wanted.)

Using separate channels for each input is a pretty simple and clean way to process things.

If you were to have a longer sequence of inputs (like 5+), it might be preferable to use a single channel/listener and a global variable (integer) to track how many inputs have been processed. It's mainly just an efficiency thing.

Edited by Wulfie Reanimator
  • Thanks 1
Link to comment
Share on other sites

It depends a lot on the actual use-case, but for situations in which I want a sequence of user-input things to happen in order, states can be a useful way of making that happen:

integer gChan = 35;

string responseA;
string responseB;

default
{   state_entry()
    {   llListen(gChan,"",llGetOwner(),"");
        llTextBox(llGetOwner(),"Question 1:",gChan);
    }
    listen(integer chan, string name, key ID, string text)
    {   responseA = text;
        state state_b;
    }
}
state state_b
{   state_entry()
    {   llListen(gChan,"",llGetOwner(),""); // listeners are reset on state change.
        llTextBox(llGetOwner(),"Question 2:",gChan);
    }
    listen(integer chan, string name, key ID, string text)
    {   responseB = text;
        // do something with the responses.
        llSay(0,responseA+" : "+responseB); // for demonstration.
    }
}

This kind of approach can get messy with duplicated code (a 'real application' might want to repeat the question on touch_start, go back to the beginning if there isn't a response within a few minutes etc etc) but can be a lot less of a spaghetti mess if the kinds of things you're asking require radically different user input at each step. (textbox, dialog, put a notecard in the prim, change my permissions, change the rotation of this thing I just rezzed so it 'looks right' etc. etc.)

Since llDialog can be used to generate a llTextBox, you can fairly easily generalize a long list of questions:

list questions =
[  "How are you today?",
   "a, b or c?",
   "Pineapple on pizza?"
];
list options =
[  "!!llTextBox!!",
   "a,b,c",
   "Eww. No!,Awesome!"
];
integer q_index;
integer n_questions = 3; //
list responses;
integer gChan = 35;

default
{  state_entry()
   {   llListen(gChan,"",llGetOwner(),"");
       llDialog(llGetOwner(),llList2String(questions,q_index),llCSV2List(llList2String(options,q_index)),gChan);
   }
   listen(integer chan, string name, key ID, string text)
   {   responses+=text;
       ++q_index;
       if(q_index<n_questions)
       {   llDialog(llGetOwner(),llList2String(questions,q_index),llCSV2List(llList2String(options,q_index)),gChan);
       }else
       {   // do something with the responses.
           llSay(0,llList2CSV(responses)); // for demonstration.
       }
   }
}

 

Edited by Quistess Alpha
  • Thanks 1
Link to comment
Share on other sites

3 hours ago, Rolig Loon said:

Normally each new dialog box or textbox should replace whatever was on the screen before, and normally clicking on the Submit button in either one should close it.

Interestingly this is viewer-dependent behavior. If the debug setting ScriptDialogLimitations is set to 4, and a script is in a HUD, (in addition to other possible combinations) then sending a new llDialog() menu to the user won't close an old one they may not have responded to or manually closed.

Quote

ScriptDialogLimitations: Limits amount of dialogs per script

(0 - per object,

1 - per channel,

2 - per channel for attachments,

3 - per channel for HUDs,

4 - unconstrained for HUDs)

 

  • Like 1
Link to comment
Share on other sites

9 hours ago, Wulfie Reanimator said:

Those settings can also be found in normal preferences, at least in Firestorm.

and in other viewers, but they're in a slightly different place in each if at all. I kinda wish 'debug settings' were less hidden to "regular users". I don't want to have to boot up firestorm whenever I'm conversing with someone and we realize some aspect of firestorm's default settings are dumb.

Link to comment
Share on other sites

On 3/13/2023 at 8:53 AM, Quistess Alpha said:

It depends a lot on the actual use-case, but for situations in which I want a sequence of user-input things to happen in order, states can be a useful way of making that happen:

Alternatively, track where user is using a global var or LSD.

For example (code not tested!):

integer gChan;

MenueShow() {
    llListen(gChan, "", llGetOwner(), "");
    string menue = "_menue:" + llLinksetDataRead("_menue");
    if ("dialog" == llLinksetDataRead(menue + ":type")) {
        llDialog(llGetOwner(), llLinksetDataRead(menue + ":caption"), llCSV2List(llLinksetDataRead(menue + ":buttons")), gChan);
    }
    else {
        llTextBox(llGetOwner(), llLinksetDataRead(menue + ":caption"), gChan);
    }
}

state Menue {
    state_entry() {
        if ("" == llLinksetDataRead(menue)) llLinksetDataWrite("_menue", "1");
        MenueShow();
        llSetTimerEvent(90);
    }

    timer() {
        llSetTimerEvent(0); 
        llOwnerSay("Timed out waiting for user response!");
        state Ready;
    }

    listen(integer chan, string s1, key id, string msg) {
        if (llGetOwner() != id) return;
        string menue = llLinksetDataRead("_menue");
        if ("1" == menue) {
            // handle the input from the textbox
            // ...
            // ...
            // ...
            // Transition to the next menu
            llLinksetDataWrite("_menue", "2");
            return;
        }
        if ("2" == menue) {
            // handle the input from the dialog
            // ...
            // ...
            // ...
            // We are done, cleanup and exit to OtherState
            llLinksetDataDelete("_menue"); 
            state OtherState;
        }
    }
}

The LSD and variable gChan needs to be initialized of course. Like this for example:

default {
    state_entry() {
        llLinksetDataWrite("_menue:1:type", "textbox");
        llLinksetDataWrite("_menue:1:caption", "Enter something here");
        llLinksetDataWrite("_menue:2:type", "dialog");
        llLinksetDataWrite("_menue:2:caption", "Choose a button");
        llLinksetDataWrite("_menue:2:buttons", "Button 1,Button 2,Button 3,Other Button");
        gChan = ((llHash((string)llGetOwner()) & 0x3FFFFFFF) | 0x20000000) + (integer)llFrand(1024);
    }
}

(If your scripts do not do LSD Reset, you can initialize the LSD by using a separate one-time-only script that you execute and afterwards just delete; the LSD contents will persist and be usable by other scripts. That will save a lot of scriptmem.)

The benefit of tracking using LSD is that if the script needs to be reset, the state persists and your script can resume where it got interrupted.

Edited by primerib1
  • Thanks 1
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 437 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...