Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Getting a return value from a sendChat callback function

August 01 (6 years ago)

So I'm trying to get a value back from this function:

function performRoll(msg, cmd) {
    sendChat(msg.who, cmd, function(ops) {
        var resultTotal = 0;
    if (ops[0].type == 'rollresult') {
        var result = JSON.parse(ops[0].content);
        var addSucc = 0;
        var strSplit = ops[0].origRoll.split('-');
        var cmds = [];
        _.each(strSplit, parseCmds, cmds);

        if (!_.isEmpty(cmds)) {
            resultTotal=processCmds(cmds, result);
        } else {
    // If there are no commands passed, the script defaults to doubling 10s, which is what this call represents.
            resultTotal=doDoubles(result, true, 0);
        } // if
// This gets the player's color, for styling the roll result HTML output in buildHTML().
        var player = getObj("player", msg.playerid);
        var outHTML = buildHTML(result, msg.content, ops[0].origRoll, player.get('color'));
// Passes the final, formatted HTML as a direct message to the chat window.
        sendChat(msg.who, '/direct ' + outHTML);
            return resultTotal;
    } else {
// Error handling.
        printError(ops[0], msg.who);
    } // if
});
} // performRoll


I'm trying to get the resultTotal variable out of the function, but it's some sort of javascript lambda function.


How can I restructure this so that I can return resultTotal back to the caller of performRoll?

August 01 (6 years ago)
The Aaron
Roll20 Production Team
API Scripter

It is an asynchronous function, you'd need to wrap it in a Promise and get the value when the Promise is fulfilled. 

August 01 (6 years ago)
The Aaron
Roll20 Production Team
API Scripter

Here's an example modified from RecursiveTable:

	const sendChatP = function(msg){
		return new Promise((resolve) =>{
			sendChat('',msg.replace(/\[\[\s+/g,'[['),(res)=>{
				resolve(res);
			});
		});
	};
That's a sendChat() replacement that returns a Promise to resolve the roll. The problem with Promises is they kind of infect everything you do, so you'd have to change your function then to do:
function performRoll(msg, cmd) {
	return new Promise((resolve,reject) => {
		sendChatP(cmd).then(function(ops) {
			var resultTotal = 0;
			if (ops[0].type == 'rollresult') {
				var result = JSON.parse(ops[0].content);
				var addSucc = 0;
				var strSplit = ops[0].origRoll.split('-');
				var cmds = [];
				_.each(strSplit, parseCmds, cmds);

				if (!_.isEmpty(cmds)) {
					resultTotal=processCmds(cmds, result);
				} else {
				// If there are no commands passed, the script defaults to doubling 10s, which is what this call represents.
					resultTotal=doDoubles(result, true, 0);
				} // if
				// This gets the player's color, for styling the roll result HTML output in buildHTML().
				var player = getObj("player", msg.playerid);
				var outHTML = buildHTML(result, msg.content, ops[0].origRoll, player.get('color'));
				// Passes the final, formatted HTML as a direct message to the chat window.
				sendChat(msg.who, '/direct ' + outHTML);
				resolve(resultTotal);
			} else {
				// Error handling.
				printError(ops[0], msg.who);
				reject();
			} // if
		});
	});
} // performRoll

// calling the function and handling the results
performRoll(msg,cmd).then(res => {
	// do something with res
}).catch(e=>{
	// handle the error case, if you want.
});




August 01 (6 years ago)

Thank you, that's a really useful function to know about! Much appreciated :D 

I'm not familiar with Javascript much (I'm a C# guy) so while I'm okay with the structure, there's still a fair bit to pick up. I've managed to write a few more functions based on what I already knew out of it but this one stumped me for a bit.

No doubt there'll be a bunch more such questions before I'm done!

August 01 (6 years ago)
The Aaron
Roll20 Production Team
API Scripter

Awesome, bring them on!  I love programming questions, just let me know how I can help!  =D

BTW, I didn't test any of that code above, so there may be lurking bugs, but you sound like you know your way around coding, so I bet you can handle it, but let me know if you get stuck.  =D

August 01 (6 years ago)

Edited August 01 (6 years ago)

I have a question pretty much along these same lines....

I'm trying to get a prompt to display, in the middle of processing, but can never seem to get it to execute.

Here's what I'm trying:

    function getSpellLevel(msg) {
        return new Promise((resolve,reject) => {
             sendChat(msg.who,"?{Cast at Level|1|2|3|4|5|6|7|8|9}",(res)=>{
                   resolve(res);
             });
        });
    }

Then the call to the above function:

 getSpellLevel(msg).then(res => {
        log("Resp: "+res[0]);
 });

Basically, the character is beginning to cast a spell (and no, I don't want to prompt them for this up front...bear with me) and if the spell allows for "higher level", I want to prompt them to see if they wish to cast it at a higher slot level.  I need to capture that response and use it later.  The log statement is just me trying to see if anything is coming back.

Unfortunately, I never get the prompt to display.  I'm assuming since there's an ID10T or chair-to-keyboard interface issue occurring.  Using Promise/async concepts in Javascript is new to me so it doesn't surprise me that its not working.


Thanks!

August 01 (6 years ago)

Edited August 01 (6 years ago)
GiGs
Pro
Sheet Author
API Scripter

I dont think you can get user prompts in this way.

Using sendChat asynchronously is for making rolls in the middle of a script, and getting the results of the roll back. I dont think there's any way to prompt for user interaction during a script in the way you are trying to achieve here.

Ugh...makes sense.  Thanks, GiGs

August 01 (6 years ago)

Edited August 01 (6 years ago)
The Aaron
Roll20 Production Team
API Scripter

Yeah, that's not going to work that way.  When you pass a function to sendChat() as the 3rd parameter, the message is only sent to the roll processor and then returned to the API.  The only way to prompt a player mid-command is to send them a chat message and let them click something in it.  Basically, you need to make your script reentrant.  Here's a simple example:

on('ready', ()=>{

    const range = (n,s=0) => [...new Array(n).keys()].map(k=>k+s);
    const buildAsk = (d,a) => `<a href="!prompt ${d.filter(f => f !=='ask').join(' ')} ${a}">${a}</a>`;
    
    on('chat:message',(msg) => {
        if('api' === msg.type && /!prompt\b/i.test(msg.content)){
            let args = msg.content.split(/\s+/);
            let who = getObj('player',msg.playerid).get('_displayname');

            let ba = (n)=>buildAsk(args.slice(1),n);

            if(args.includes('ask')){
                sendChat('Prompt',`/w "${who}" Cast at Level: ${range(9,1).map(ba).join('')}`);
            } else {
                sendChat('Prompt', `Query was: ${args.slice(1).join(' ')}`);
            }
        }
    });
});
Run it like this:
!prompt cast fireball 3

You'll get echoed out what you said. 

Run it like this:

!prompt cast fireball ask
and it will prompt you for a level, when you click the level, it will show you what you picked along with your previous commands.

You can see a more complicated example of this in my Mutant Year Zero script.  In the above, you can keep clicking the buttons and repeating the effect.  In Mutant Year Zero, it saves the data in the state and has a token in the buttons to look up the data, and removes it when it's been used.

Thanks, Aaron!!  I'll give that a try and let you know how it goes.

August 01 (6 years ago)
Victor B.
Pro
Sheet Author
API Scripter

This is how Robin does it in Combattracker.  

'!condition remove '+key+' ?{Are you sure?|Yes,yes|No,no}

He then parses the yes/no and has an If condition around the remove.  When casting a spell, you could do the same thing.  

August 02 (6 years ago)


The Aaron said:

Awesome, bring them on!  I love programming questions, just let me know how I can help!  =D

BTW, I didn't test any of that code above, so there may be lurking bugs, but you sound like you know your way around coding, so I bet you can handle it, but let me know if you get stuck.  =D

Brilliant, thank you. I hadn't spotted that it was an async callback, and ended up putting the follow-on code in the callback itself, so now I modified Mike Leavitt's dice roller script to do initiative rolls, attack rolls and withering damage, even a simple targeted attack script. Probably messy, but it's progress from "I have no idea how to do this"!

That thing with the re-entrant scripts prompting for something is absolutely excellent. I'll have to use that... somewhere :)

I've more questions about the exalted 3e character sheet, but I guess that goes in a different forum..

August 02 (6 years ago)
The Aaron
Roll20 Production Team
API Scripter

Excellent!