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

Dynamically generate inline roll

1667946864

Edited 1667946916
I'm working on the character sheet for Infinity 2d20, which has a rolling system that's a bit complex to implement in Roll20: each d20 which rolls under a target number is 1 success. If a d20 rolls under the character's Focus in a given skill, that counts as 2 successes. In the past I used a very complex inline roll string for this, but that is unmaintainable; I have no idea how I got it to work in the first place. I've managed refactor the rolling system using Custom Roll Parsing, but now I'm faced with the annoying task of including an individual worker script for each attribute that can be rolled against. This is doable with some external scripting to generate the HTML and Javascript, but I can't help wondering if there is an easier way to do this. Here's what I use right now for each attribute/skill: <!-- HTML --> <button type="roll" name="roll_SKILLNAME" value="@{SKILLNAME_action}"></button> <button type="action" name="act_SKILLNAME-action" class="hidden"></button> <input type="hidden" name="attr_SKILLNAME_action" value=""> // Scripts on("clicked:SKILLNAME-action", (info) => { // Include one version of this script for every skill/attribute on the sheet. startRoll("&{template:skillroll} {{target_number=[[?{Attribute|Agility,@{agility}|Awareness,@{awareness}|Brawn,@{brawn}|Coordination,@{coordination}|Intelligence,@{intelligence}|Personality,@{personality}|Willpower,@{willpower}}[Attribute]+@{skill_SKILLNAME_exp}[Skill EXP]]]}} {{roll=[[?{Number of dice|2}d20cf>[[21-?{Complication Range|1}]]cs<[[@{skill_SKILLNAME_foc}]]]]}} {{header=SKILLNAME Check}} {{subheader=@{character_name}, Difficulty ?{Difficulty|1}}} {{skill_foc=[[@{skill_SKILLNAME_foc}]]}}", (allRolls) => { const computed = calcSkillRoll( // Counts the number of successes in roll. allRolls.results.roll.dice, allRolls.results.target_number.result, allRolls.results.skill_foc.result ).successes; const computedResults = { roll: computed }; finishRoll( allRolls.rollId, computedResults ); }); }); There's a couple of helper scripts with this, but these two sections have to be included for each rollable attribute. This is not only a pain to maintain, but it's a massive number of lines that just feel wasted. I had the idea of instead using a generic function that has the relevant attributes passed to it. In concept, it would look something like this: <!-- HTML --> <button type="roll" name="roll_SKILLNAME" value="@{SKILLNAME_action}"></button> <button type="action" name="act_generic-roll-action" class="hidden" value="SKILLNAME,ATTRIBUTENAME" ></button> <input type="hidden" name="attr_SKILLNAME_action" value=""> // Scripts on("clicked:generic-roll-action", (info) => { // Just one of these is necessary.         const rollString = generateRollString(info.value.SKILLNAME, info.value.ATTRIBUTENAME); // Helper function that would replace the necessary sections in the generic roll to create the actual inline roll string. startRoll(rollString, (allRolls) => { const computed = calcSkillRoll( // Counts the number of successes in roll. allRolls.results.roll.dice, allRolls.results.target_number.result, allRolls.results.skill_foc.result ).successes; const computedResults = { roll: computed }; finishRoll( allRolls.rollId, computedResults ); }); }); I actually got this working with jQuery! To my dismay though, it seems that jQuery isn't yet on the main servers. I then thought that I could use the HTMLAttributes from the action button eventInfo as parameters, but this doesn't work since the method I'm using doesn't pass any information about the HTML action button to the script. The only info that is  passed is the name tag, which I can't really use as a parameter since that's what calls the script. Is there some other method I'm missing that would let me pass parameters to a sheet worker?
1667958195
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
You can do this by making your roll function more generic and programmatically providing the skill names: const skills = ['skillname1','skillname2',/* include all your skill names here */]; skills.forEach((skill) =>{// Iterate over each skill on(`clicked:${skill}-action`, (info) => { //Use template literals to dynamically create the listen string startRoll(`&{template:skillroll} {{target_number=[[?{Attribute|Agility,@{agility}|Awareness,@{awareness}|Brawn,@{brawn}|Coordination,@{coordination}|Intelligence,@{intelligence}|Personality,@{personality}|Willpower,@{willpower}}[Attribute]+@{skill_${skill}_exp}[Skill EXP]]]}} {{roll=[[?{Number of dice|2}d20cf>[[21-?{Complication Range|1}]]cs<[[@{skill_${skill}_foc}]]]]}} {{header=${skill} Check}} {{subheader=@{character_name}, Difficulty ?{Difficulty|1}}} {{skill_foc=[[@{skill_${skill}_foc}]]}}`, (allRolls) => { const computed = calcSkillRoll( // Counts the number of successes in roll. allRolls.results.roll.dice, allRolls.results.target_number.result, allRolls.results.skill_foc.result ).successes; const computedResults = { roll: computed }; finishRoll( allRolls.rollId, computedResults ); }); }); }); As for jQuery, note that there is a very limited version of jquery that is available. Essentially class add/remove/toggle and the jquery .on . Except in very specific circumstances, I'd avoid using the jquery listener as it is not callable from chat or invokable by the API. And, I'd avoid relying on the HTMLAttributes property of the roll buttons as they are not provided if someone calls the macro from chat (either by typing out the ability invocation like %{my char|skill-action} , or by using an ability button that they dragged to their quick bar).
Wow, I didn’t realize I could use listeners like that. That looks like it should work perfectly; thank you!
1667959581
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Yep, it's the technique that the K-scaffold is built on and allows sheets to really abstract away the listener architecture. Heck with a K-scaffold sheet, you really only have one listener, and then a map of what functions to call for a given event.
1668108188

Edited 1668108389
I got rolls working perfectly for all of my static attributes, but now I'm running into issues implementing repeating sections. I've managed to get the inputs in each repeating section updating using your tips from a few months ago , but when I click them the chat outputs " No ability was found for %{TestCharacter|weapon-damage-action} ". I added the generic action name to the event listener's list, but it's not picking up on when I click the repeating action button. Here's a cut-down form of what I have currently: <fieldset class="repeating_weapons"> <button type="roll" name="roll_weapon_damage" value="@{weapon_damage_action}"></button> <button type="action" name="act_weapon-damage-action" class="hidden"></button> <!-- The value of the input field is properly updating. This is what I currently have being filled in. --> <input type="hidden" name="attr_weapon_damage_action" value="%{TestCharacter|weapon-damage-action}"> </fieldset> // List of all actions. const attrActions = [ 'weapon_damage', // (other actions)... ]; // Event listener for all rolling buttons. attrActions.forEach((attr) => { const attrAct = attr.replaceAll('_','-'); on(`clicked:${attrAct}-action`, (info) => { const rollStr = generateRoll(attr); // Process the roll from rollStr. }); }); Do I need to instead add the ID of the repeating section somewhere? I tried that in the input field by filling in "%{TestCharacter| repeating_weapons_-ngxjhum7rmqt4ialp8s-weapon-damage-action }" (with that row's ID) but that didn't work. Do I need to specify row ID in the event listener?
1668109131

Edited 1668109483
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
A few things to note about the roll button/action button combo construction and the associated sheetworker: You do not define the value of the hidden action button inside the html. The  actionCallUpdate  sheetworker from the post you linked handles populating this with the correct value. When using the code from that forum post, you need to make sure to properly declare all of your repeating sections in the repeatingSections  array. If your repeating section is not defined there, the setting of the repeating ability calls will be incorrect or fail out right I also notice that you have `weapon_damage` in your attrActions  array, which likely is not correct. To listen for clicks on the repeating action buttons, you need to specify the repeating section that the button resides in and the full button name like so: // List of all actions. const attrActions = [ 'repeating_weapons:weapon-damage-action', // (other actions)... ]; Edit: Ah, I see that you're converting it to an action button name in your forEach . So to handle all the permutations that can occur, you'll need to rejigger your code a little: <fieldset class="repeating_weapons"> <button type="roll" name="roll_weapon_damage" value="@{weapon_damage_action}"></button> <button type="action" name="act_weapon-damage-action" class="hidden"></button> <!-- The value of the input field is properly updating. This is what I currently have being filled in. --> <input type="hidden" name="attr_weapon_damage_action" value=""> </fieldset> // List of all actions. const attrActions = [ 'repeating_weapons:weapon-damage-action', // (other actions)... ]; // Event listener for all rolling buttons. attrActions.forEach((act) => { const attr= act         .replace(/-/g,'_')         .replace(/^repeating_.+?\:/,'')         .replace(/_?action$/,''); on(`clicked:${act}`, (info) => { const rollStr = generateRoll(attr); // Process the roll from rollStr. }); });
1668110774

Edited 1668110861
I wasn't able to get the ActionCallUpdate sheetworker to work for me; I kept getting an error stating "queue.shift() is not a function". I instead wrote my own version that's working for now. Adding the repeating section to the listener array worked! For my own future reference, this is what is required for repeating section buttons: <fieldset class="repeating_weapons"> <button type="roll" name="roll_weapon_damage" value="@{weapon_damage_action}"></button> <button type="action" name="act_weapon-damage-action" class="hidden"></button> <!-- Value of the input needs to be updated whenever a row is added, the sheet is opened, or the character name is changed. -->      <!-- Value needs to specify the character name, repeating section, rowId, and action name. --> <input type="hidden" name="attr_weapon_damage_action" value=" %{TestCharacter|repeating_weapons_${rowId}_weapon-damage-action} "> </fieldset> // List of all actions. // Repeating section actions need to have the section name prefaced with a colon. const attrActions = [ ' repeating_weapons:weapon-damage-action ', 'normal-action', (other actions)... ]; // Event listener for all rolling buttons. attrActions.forEach((attr) => { on(`clicked:${attr}`, (info) => { // Roll parsing goes here. }); });