Invisible Rolls and Nesting startRoll

The last month of posts have been demonstrating how to make opposed rolls using Custom Roll Parsing. One downside of the approach decided is that it assumes all rolls are opposed, and you must always target an enemy to make a roll.

But Pendragon assumes a lot of rolls will be personal and unopposed. We could create two buttons for every rating – for opposed and unopposed actions – but that could eat up a lot of space when you take into account that Pendragon uses rolls for each skill, stat, personality trait, passion, and maybe more. Wouldn’t it be nice if we could double up opposed and unopposed rolls on the same button?

It turns out we can, and there’s more than one way to do it.

The Silent Roll, and Nesting startRoll

In an earlier post about asking the player, we demonstrated the ability to ask the player questions. We are going to do that again here, with this code:

Object.keys(ratings).forEach(rating => {
    on(`clicked:${rating}`, () => {
        opposed_or_not(rating);
    });
});

const opposed_or_not = (rating = '') => {
    const question = `Type of Roll|Opposed,0|Unopposed,1`;
    startRoll(`!{{ask=![[?{${question} }]]}}`, (question) => {
        const query = question.results.ask.result;
        let roll_string;
        if(query) {
            roll_string = `&{template:pendragon} {{title=[[0]]}} {{rating=${ratings[rating]}}} {{die=[[1d20]]}} {{score=[[@{${rating}}]]}} {{modifier=[[?{modifier|0}]]}} {{who=[[0@{${who}}]] }} {{opposed=[[0]]}}`;
        } else {
            roll_string = `&{template:pendragon} {{title=[[0]]}} {{rating=${ratings[rating]}}} {{die=[[1d20]]}} {{score=[[@{${rating}}]]}} {{modifier=[[?{modifier|0}]]}} {{who=[[0@{${who}}]] }} {{target_rating=[[0@{target|last_rating}]]}} {{target_die=[[@{target|last_die}]]}} {{target_score=[[@{target|last_score}]]}} {{target_modifier=[[@{target|last_modifier}]]}} {{who=[[0@{character_name}]] }} {{target_who=[[0@{target|token_name}]] }} {{outcome=[[0]]}} {{opposed=[[0]]}}`;
        }
        do_roll(roll_string, rating);
    });
};Code language: JavaScript (javascript)

This code assumes you already have the do_roll function along with various bits and pieces from the last post (like CSS). Follow the logic, and notice how the query is presented as a startRoll function, and inside do_roll is another startRoll function. This is fine – you can nest them inside of each other.

Now, when a roll is made, the active player will be presented with two options:

If they choose, Opposed, the worker will roll your selected ability, and then prompt you to select a target, and check if they have a value to compare against.

If you choose Unopposed, your roll will be made and no target option will be presented. You are encouraged to flip the order if unopposed actions are more common in your game – make the top one the most frequently used option.

Even More Options

Let’s say the GM considers that opposed/unopposed option to be too onerous, and wants the ability to set whether a roll is opposed or unopposed separately. For example, you know the players are in feast scene and the next several rolls are all unopposed. Then they are in combat, and have to make a lot of opposed rolls. So, you want to flick a button to change the type of roll ahead of time.

But since you don’t always know what the game will be like, you want to keep the option of presenting a menu where the players can choose on a case-by-case basis.

First, you create a new attribute to hold the type of roll it will be. This is created in the HTML.

<input type="hidden" name="attr_type_of_roll" value="0">
<!-- this can have three values
   0 = present a menu as normal
   1 = the roll is an opposed roll
   2 = the roll is an unopposed roll
-->Code language: HTML, XML (xml)

You need some way to switch between modes. We’ll create 4 buttons, and 4 of them will be hidden – you’ll see why soon.

<button type="action" name="act_type_choose">Type of Roll</button>
<button type="action" name="act_type_0" class="hidden"></button>
<button type="action" name="act_type_1" class="hidden"></button>
<button type="action" name="act_type_2" class="hidden"></button>Code language: HTML, XML (xml)

Only the first of these is needed, but the rest allow the GM to create a macro like this:

!&{bob|type_?{query|Which Type?|0}} &{Phil|type_?{query|Which Type?}} &{emily|type_?{query|Which Type?|0}}Code language: Markdown (markdown)

This lets the GM automatically update all 3 character sheets with a single macro, and switch between settings on the fly.

Okay, then we need two sheet workers – one for that first button, above, and then a modification of the roll worker shown earlier.

on('clicked:type_choose', () => {
    startRoll(`!{{ask=[[?{Type of Roll|Menu,0|Opposed,1|Unopposed,2} ]] }`, (question) => {
        const query = question.results.ask.result;
        setAttrs({
           type_of_roll: query
        });
        finishRoll(question.rollId);
    });
});Code language: JavaScript (javascript)

The only reason finishRoll is there because each startRoll function needs it – it isn’t actually used.

When asking a question in this way, the answer must be numerical (hence the [[ ]] brackets). CRP cannot interpret anything but numbers.

Finally, we need to update the event workers for the actions.

Object.keys(ratings).forEach(rating => {
    on(`clicked:${rating}`, () => {
        menu_or_not(rating);
    });
});

const menu_or_not = (rating = '') => {
    getAttrs(['type_of_roll'], v => {
        const type_of_roll = +v.type_of_roll || 0;
        let roll_string;
        if(type_of_roll == 1) {
            roll_string = `&{template:pendragon} {{title=[[0]]}} {{rating=${ratings[rating]}}} {{die=[[1d20]]}} {{score=[[@{${rating}}]]}} {{modifier=[[?{modifier|0}]]}} {{who=[[0@{${who}}]] }} {{target_rating=[[0@{target|last_rating}]]}} {{target_die=[[@{target|last_die}]]}} {{target_score=[[@{target|last_score}]]}} {{target_modifier=[[@{target|last_modifier}]]}} {{target_who=[[0@{target|token_name}]] }} {{outcome=[[0]]}} {{opposed=[[0]]}}`;
        } else if(type_of_roll == 2) {
            roll_string = `&{template:pendragon} {{title=[[0]]}} {{rating=${ratings[rating]}}} {{die=[[1d20]]}} {{score=[[@{${rating}}]]}} {{modifier=[[?{modifier|0}]]}} {{who=[[0@{${who}}]] }} {{opposed=[[0]]}}`;
        } else {
            const question = `Type of Roll|Opposed,0|Unopposed,1`;
            startRoll(`!{{ask=![[?{${question} }]]}}`, (question) => {
                const query = question.results.ask.result;
                let roll_string;
                if(query) {
                    roll_string = `&{template:pendragon} {{title=[[0]]}} {{rating=${ratings[rating]}}} {{die=[[1d20]]}} {{score=[[@{${rating}}]]}} {{modifier=[[?{modifier|0}]]}} {{who=[[0@{${who}}]] }} {{opposed=[[0]]}}`;
                } else {
                    roll_string = `&{template:pendragon} {{title=[[0]]}} {{rating=${ratings[rating]}}} {{die=[[1d20]]}} {{score=[[@{${rating}}]]}} {{modifier=[[?{modifier|0}]]}} {{who=[[0@{${who}}]] }} {{target_rating=[[0@{target|last_rating}]]}} {{target_die=[[@{target|last_die}]]}} {{target_score=[[@{target|last_score}]]}} {{target_modifier=[[@{target|last_modifier}]]}} {{target_who=[[0@{target|token_name}]] }} {{outcome=[[0]]}} {{opposed=[[0]]}}`;
                }
                do_roll(roll_string, rating);
                return;
            });
        }
        do_roll(roll_string, rating);
    });    
};Code language: JavaScript (javascript)

This code is a mess, but if you examine it, you’ll see it is very similar to the code that has gone before. We have a return statement in there to make sure the code ends at that point and doesn’t continue past the if statement and run a second do_roll.

In Conclusion

At this point we have created a roll for Pendragon, shown how to make unopposed rolls work, and give them GM a lot of power to control whether players use opposed and unopposed rolls. There are other games that use opposed rolls, and we may demonstrated a few more in the days to come.

Leave a Reply

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