Sending Messages and Asking Questions in Roll20 Sheets

Send Message

Sometimes you want to send some text to chat, like “this button on this sheet was clicked”, or “Sandra healed their HP by 3 points”.

You can do this with CRP, and here’s a generic function to do that. I’ll explain how it works, and potential pitfalls, below the code.

    /* 
        msg: the output you want to show
        title: should be full section including the key, but without the {{ }} part, like 'This is @{character_title}'s roll', and without the name= part ofnusing the default rolltemplate.
        template: a single word saying which template to use, and if skipped will be default.

        send_message('this is a message');
        send_message('this is a message', 'this is a title')
        send_message('this is a message', '', 'custom')
        send_message('this is a message', 'title=this is a title', 'custom')
    */
    const send_message = (msg, title='', template='default') => {
        startRoll(`&{template:${template}} ${title ? (template==='default' ? `{{name=${title} }}` : `{{${title} }}`) : '' } {{${msg}}}`, no_roll => {
            finishRoll(no_roll.rollId)
        });
    };
    {{{{/noop}}}}Code language: JavaScript (javascript)

The comment block at the start just tells you how to use it and gives examples of possible usage. You call it with send_message(something) and can just include that inside any other sheet worker.

You must include a message, like "a button was clicked", and if you want to include a key, you need to include that too like "desc=a button was clicked".

If you want to include a title, you must include the key for a title and the = sign, like title=. You don’t need to do this if the rollTemplate used is default. It is assumed the title key is name.

Finally, if not using the default template, you can enter the name of the template you are using.

The body of the sheet worker then just builds a string to send to the roll, but here the “roll” doesn’t include a roll. You can include this function in workers that already include rolls.

If you want to pass properties (like “heals 3 hit points” your sheet worker needs to work those out, and pass them in the msg.

Just copy this code (you don’t need the comments) and place it at the start of your script block ,and you can use it in any sheet worker.

Prompting For a Response

Another thing you can do (and something I’ve probably been doing too much lately), is ask for permission, and only proceed with the rest of the worker if permission is granted. For example, imagine a player wants to refresh themselves but can only do that once per day and doing so affects multiple attributes on the character sheet (like all spell slots, hit points, and more). So if the player does it, there’s no easy way to undo it.

Now imagine including a prompt that says, “Are you sure about this? It will affect a bunch of things.” Here the player gets a moment to pause and decide if they really want to do it. When it is done, you could also send a message to chat that this has been done.

(You could also include other Booleans that give permission to click that button once, which are refreshed when you start another day – but that’s a different topic).

This is a little more complex than the send_message function because, as with getAttrs and getSectionIDs, the code you want to run after the question must be nested inside the startRoll function. That complicates the creation of a function, but here’s one way to do it.

First, create this function and place it at the start of your script block. Then leave it well alone – make no changes to it. Now, I’ll explain what it does and how to use it.

const ask = (prompt, callback) => {
    const report_string = `!{{ask=[[?{${prompt}|Cancel,0|Do It,1}]]}}`;
    startRoll(report_string, question => {
        const query = question.results.ask.result;
        if(query) {
            callback();
        }
    });
};Code language: JavaScript (javascript)

This is a callback function, and is called from someone else in your code. The idea is, anywhere you want players to be presented with a prompt: “should I do this?” and exactly two choices, “Cancel” and “Do It”, you can call this function.

I’ll first show how you might use it. Look for the word ask in the following worker.

Imagine you have a button that heals a character up to maximum hit points, but asks the player for permission before they use it. That worker could look like this.

on('clicked:recover', () => {
    getAttrs(['hits', 'hits_max'], values => {
        const hits = values.hits;
        const max = values.hits_max;
        const heal = max - hits;

        const message = `@{character_name} heals ${heal} points.`;

        const prompt = `Do you want to heal ${heal} points?`;
        ask(prompt, callback => {
            send_message(message);
            setAttrs({
                hits: max
            });
        });
    });
});
Code language: JavaScript (javascript)

To test this code, I created the following HTML:

Hits:
<input type="number" name="attr_hits" value="10">
<input type="number" name="attr_hits_max" value="30" readonly>
<button type="action" name="act_recover">Recover</button>Code language: HTML, XML (xml)

That created an input to hold the current hit points and the maximum., and a button to launch the healing worker.

The start of the worker just grabs hits and max, and calculates the difference. The we have this section:

        const prompt = `Do you want to heal ${heal} points?`;
        ask(prompt, callback => {
            send_message(message);
            setAttrs({
                hits: max
            });
        });Code language: JavaScript (javascript)

This creates a prompt variable, a message text, and then the ask function which must be exactly that line.

After that line, you code what happens if the player selected Do It. If they select Cancel, it does not happen. In other words, you have something like this:

        const prompt = `TEXT TO DISPLAY IN PROMPT`;
        ask(prompt, callback => {
            /* the code to be run if DO IT is chosen */

        });Code language: JavaScript (javascript)

That’s all you really neesd to know. Include those two lines, and t will just work. But if you want to now how it works…

How It Works

The ask function contains this code.

const ask = (prompt, callback) => {
    const report_string = `!{{ask=[[?{${prompt}|Cancel,0|Do It,1}]]}}`;
    startRoll(report_string, question => {
        const query = question.results.ask.result;
        if(query) {
            callback();
        }
    });
};Code language: JavaScript (javascript)

This code is run whenever you have that ask line.

report_string is the “roll” being fed to a custom roll parsing startRoll function. Normally this would look something like const report_string = `&{template:default} {{ask=[[?{${prompt}|Cancel,0|Do It,1}]]}}`; but we don’t need to define a rolltemplat because this won’t appear in cat.

We do need to start with ! – that is the character that tells roll20, “what follows is just coding, and won’t be sent to chat”.

It must be a numerical inline roll (a number inside [[ ]] brackets), because Roll20 does not create usable roll results unless it is an inline roll and a number.

Then startRoll is run, and creates a “roll” called question. When you have a Custom Roll Parsing object, you can look inside the results property, get a specific key (in this case ask), and report its result. This is what we do with question.results.ask.result.

The tresult will always be 0 or 1. These are falsy and truthy values. In JavaScript, when you use if(expression), the expression can simply be a property and if its “truthy” then the contents of the if are done. Here that means the callback() function.

One of the interesting things about JavaScript is that you can pass complete functions. So we have this code:

       ask(prompt, callback => {
            send_message(message);
            setAttrs({
                hits: max
            });
        });Code language: JavaScript (javascript)

That is saying, “assign everything inside the => { } part to callback”. That becomes a function, which we send to the ask function.

Everything after the ask line is run only if the user clicks Do It.

Possible Drawbacks

This uses the startRoll function, and that is an asynchronous function. That means in a worker heavy with asynchronous functions, this might cause lag. This probably isn’t a serious worry, but it’s something to bear in mind.

The ask function is also designed for exactly two options – Yes and No (values of 1 and 0 respectively). You can make custom versions that manage more than 2 options. I might tweak this later so you can supply your own list of options. But for now it’s for exactly two options- yes and no.

In Summary

We have shown how to create two generic functions that you can place in any worker. send_message is used to print text to chat, and ask is used to limit code to only run when the player picks Do It in a prompt.

These are both very general and are designed to be used in as many workers as you like, whenever you need them. Have fun with them.

What other ways have you used Custom Roll Parsing, and what things would you like to do?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.