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

Custom Checkboxes to Action Buttons for some Custom Roll Parsing Action?

So, I have set up my sheet aesthetically...and then I was about to do the next step...putting in rolls/sheet workers...and I realize...I think I might be in a bit of a pickle! I've cut the code down for just a visible test.  This is for Night's Black Agents, Solo Ops. The section for General Skills. Let's go with one skill--Athletics.  The actual skill has more than just what I'm showing...but I hope I can explain the larger context. So Skills default to 2 Dice (which I'm calling the athletics_max). Each time a skill is used, it depletes down one die.  When the PC "Takes time" the dice are refreshed back up to the max. The max can be permanently increased to 3 dice by spending experience. Skills also can be stunted (which mean you deplete 2 dice in a complimentary skill to get a bonus die on your roll. Die mechanics? The player has a number of dice to roll. They have a target number they want to achieve with the dice they have. They roll the dice one at a time noting the running total. They can stunt or take on a problem or get aid from a contact, or spend an Edge to get extra dice. If they hit their target number while still having dice leftover they get a bonus.  Here is what the sheet looks like in general: The diamonds on the left are checkboxes that hold the max attribute for the skill (attr_athletics_max). The target a checkbox representing stunting (attr_athletics_stunt). The 3d6s are checkboxes representing how many dice you have to roll with that skill (attr_athletics). The diamonds on the left are checkboxes holding xp you might have invested in improving the skill (attr_athletics_xp). So everything is a checkbox that sets the attributes. I've figured out how to set up the d6's so that if you click on one of the dice, all the dice before it are red, but after are grey. Clicking on an already checked dice resets the dice to 0 and they are all grey. I love this. Everything there is working great! However...my next goal is turning those d6s into roll buttons...well...I guess they would have to be action buttons.  What I wanted to happen...(assuming you are at 2 dice) You click the third die? Nothing happens. You click the first die? Nothing happens. You click the second die? A pop-up happens asking if you have any bonus die and if so, how many? If you do it adds that number to the total number of die you will be rolling. You roll each die individually, after each die you are asked if you want to roll the next die or add more bonus die or if you want to stop. If you meet the target number you advance, if you meet the target number and have dice life over you Advance and get a bonus. David M. figured out how to do this using Script Cards. I need to figure out how to a) have a sheet trigger a scriptcard macro, but also b) figure out how to do this with a custom roll parser, because I like to know how to do things multiple ways. After the die roll is done, the attr_athletic number is decremented and the dice colors are changed accordingly, and what buttons you can push are adjusted. Other things: If you have 2+ dice in a skill, the stunt checkbox is available. If you click it, it decrements that skill by 2 and deactivates that stunt button. If you have less than 2 dice in a skill, that stunt checkbox is greyed out. Pressing the Take Time button resets everything. So where I'm at right now is the first step. I just want to figure out the d6 part. How to turn those checkboxes into action buttons that change the attr_athletics variable with accompanying button availability and styling, and launches the roll protocol (which I'm not working on just yet). My current code (with the checkboxes) are like so: <div class = "section general" > <h2> General Abilities </h2> <div class = "dieholdergrid" > <input type = "checkbox" id = "athleticsdie0" value = "0" name = "attr_athletics_die" class = "dietracker0 dietracker" /> <label for = "athleticsdie0" class = "dietracker0 dietrackerL" ></label> <input type = "checkbox" id = "athleticsdie1" value = "1" name = "attr_athletics_die" class = "dietracker1 dietracker" /> <label for = "athleticsdie1" class = "dietracker1 dietrackerL" > F </label> <input type = "checkbox" id = "athleticsdie2" value = "2" name = "attr_athletics_die" class = "dietracker2 dietracker" checked /> <label for = "athleticsdie2" class = "dietracker2 dietrackerL" > F </label> <input type = "checkbox" id = "athleticsdie3" value = "3" name = "attr_athletics_die" class = "dietracker3 dietracker" /> <label for = "athleticsdie3" class = "dietracker3 dietrackerL" > F </label> </div> </div> CSS: /* GENERAL SKILLS DETAILS */ /* Die Tracker */ div.dieholdergrid { background : transparent ; display : grid ; column-gap : 6px ; padding-right : 8px ; height : 100% ; align-items : center ; width : min-content ; margin-bottom : 10px ; } div.dieholdergrid > .dietracker0 { grid-column : 1 ; grid-row : 1 ; } div.dieholdergrid .dietracker0 { display : none !important ; } .dieholdergrid > .dietracker1 { grid-column : 2 ; grid-row : 1 ; } .dieholdergrid > .dietracker2 { grid-column : 3 ; grid-row : 1 ; } .dieholdergrid > .dietracker3 { grid-column : 4 ; grid-row : 1 ; } div.dieholdergrid input { opacity : 0 ; } label.dietrackerL { font-family : 'dicefontd6' ; font-size : 2.5em ; content : 'F' ; display : grid ; height : 20px ; width : 20px ; z-index : 9 ; color : var ( --blood-color ); } .dietracker:checked + label { color : var ( --blood-color ); } .dietracker:checked + label ~ label { color : #aaa ; } I was playing around with action button stuff and this is what I wrote so far: <div class = "dieholdergridA" > <input type = "hidden" name = "attr_athletics_die" value = "0" > <button class = "d6tracker d6tracker1 athletics" type = "action" name = "act_athlectics" ></button> <button class = "d6tracker d6tracker2 athletics" type = "action" name = "act_athlectics" ></button> <button class = "d6tracker d6tracker3 athletics" type = "action" name = "act_athlectics" ></button> </div> CSS: /* GENERAL SKILLS DETAILS */ /* Die Tracker Action */ div.dieholdergridA { background : transparent ; display : grid ; column-gap : 8px ; padding-left : 8px ; height : 100% ; align-items : center ; width : min-content ; margin-bottom : 10px ; } .dieholdergridA > button [ type = action ] .d6tracker { background-color : transparent ; padding : 0 ; border : 0 ; margin : 0 ; } .dieholdergridA > button [ type = action ] .d6tracker::before { font-family : 'dicefontd6' ; font-size : 3em ; content : 'F' ; display : grid ; height : 20px ; width : 20px ; color : var ( --blood-color ); } .dieholdergridA > .d6tracker1 { grid-column : 1 ; grid-row : 1 ; } .dieholdergridA > .d6tracker2 { grid-column : 2 ; grid-row : 1 ; } .dieholdergridA > .d6tracker3 { grid-column : 3 ; grid-row : 1 ; } So my first pass at turning the checkboxes into action buttons was successful insofar as I got them showing up and red. I hadn't gotten to the point where I could turn them off or on by clicking on them or changing their colors.  So my current thoughts-- Action Buttons can't have values attached to them it seems...that seems less useful...but I might be able to have the sheetworker of any given die grouping...setAttr of the attr_athletics...somehow. But...the buttons don't have variables...but maybe I could do it through the classes somehow? Or maybe...I could create a die Object that has a button as one of its variables? And maybe also a variable representing the athlectics skill? Maybe even a variable for the images?...would that work? Anyhow, insights from all you smart people would be awesome!
1720925915
GiGs
Pro
Sheet Author
API Scripter
That sounds like a very complex system, and it's good tobreak it down to manageable steps. I notice straight off the bat you have an error in your HTML. Ive trimmed the code down to just the relevant parts: <input type = "checkbox" value = "1" name = "attr_athletics_die" > <input type = "checkbox" value = "2" name = "attr_athletics_die" > <input type = "checkbox" value = "3" name = "attr_athletics_die" > These are referring to the same checkbox, not different boxes - even though the value is different. Either you need to give them unique names, or change the type to radio . (There are ways to use a multiple checkboxes with different values, but only one will ever be checked - if you check any, the others become unchecked.) If you are replacing those checkboxes with action buttons, this point is moot. You roll each die individually, after each die you are asked if you want to roll the next die or add more bonus die or if you want to stop. If you meet the target number you advance, You can't do this with a character sheet. You noted that David M figured out how to do this with ScriptCards - but that needs a Pro subscription. Using the code available to character sheets, you cannot break a single roll into multiple steps like this. You could use Custom Roll Parsing to respond to a single click, and then save the current total in a hidden input and make whatever changes are needed. Then players could click the second die, add it to the total. Then click the third. But you have ti break it down into discrete steps like this - every decision point must be a new action button.
Hello, hello! GiGs said: That sounds like a very complex system, and it's good tobreak it down to manageable steps. I notice straight off the bat you have an error in your HTML. Ive trimmed the code down to just the relevant parts: <input type = "checkbox" value = "1" name = "attr_athletics_die" > <input type = "checkbox" value = "2" name = "attr_athletics_die" > <input type = "checkbox" value = "3" name = "attr_athletics_die" > These are referring to the same checkbox, not different boxes - even though the value is different. Either you need to give them unique names, or change the type to radio . (There are ways to use a multiple checkboxes with different values, but only one will ever be checked - if you check any, the others become unchecked.) If you are replacing those checkboxes with action buttons, this point is moot. I was following the lead of a few other sheets that took advantage of using the same attribute for multiple checkboxes so that it works like a radio button while still being a checkbox. I was looking at Sig: City of Blades Sheet especially. So only one checkbox is checked at a time, that one being the score in the athletics attribute, and it also allows an easy resetting due to a hidden checkbox 0. I did some testing of the variables using console.info() and it seems like there aren't any errors and everything is saving properly. Will this be a future problem?! You roll each die individually, after each die you are asked if you want to roll the next die or add more bonus die or if you want to stop. If you meet the target number you advance, You can't do this with a character sheet. You noted that David M figured out how to do this with ScriptCards - but that needs a Pro subscription. Using the code available to character sheets, you cannot break a single roll into multiple steps like this. You could use Custom Roll Parsing to respond to a single click, and then save the current total in a hidden input and make whatever changes are needed. Then players could click the second die, add it to the total. Then click the third. But you have ti break it down into discrete steps like this - every decision point must be a new action button. I used the script cards macro David M made and then put it on a roll button in the sheet to test to see if it would call it, and it did. So I guess the way to get this to really work is to have the action buttons call the script cards macro. Which means...I think I won't be able to share the sheet, right? But at least I can use it for my campaigns.  So I think I need to have the action button change the variables and then call the macro through the start roll function...or something along those lines?
1720935011
GiGs
Pro
Sheet Author
API Scripter
Regarding the checkbox with different values: "Will this be a future problem?!" It shouldn't be. You can do that, but a restyled radio button would probably be easier to use. I guess those who you got the idea from really wanted the checkbox look and didn't restyle radio buttons to look like that (which is understandable - I don't like to mess with CSS if I can avoid it - the checkbiox approach might be easier in some ways). Regarding tbhe ScriptCards approach: TrooperSJP said: I used the script cards macro David M made and then put it on a roll button in the sheet to test to see if it would call it, and it did. So I guess the way to get this to really work is to have the action buttons call the script cards macro. Which means...I think I won't be able to share the sheet, right? But at least I can use it for my campaigns.  So I think I need to have the action button change the variables and then call the macro through the start roll function...or something along those lines? I think Roll20 will accept a sheet that uses Scripts like ScriptCards (at least, tehy used to), but they ask you to make the Scripts an option so that people who are not Pro users can still use the sheet. You'd need a setting to switch between te scriptcards approach and a basic, non-scripted version: you could make this as simple as you wanted, like a roll button that rolls 1d6 - then the players sort out their rolls. For your last question, that sounds right. Or you could just use a roll button sending the macro, and change the scriptCards macro to also change the stats as you need them. It can do that, I believe.
1720936820
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
The other reason to use check boxes instead of radios is if you need to be able to clear/reset the attribute value to 0, which I think is possible in this system if I'm reading the breakdown right.
Scott C. said: The other reason to use check boxes instead of radios is if you need to be able to clear/reset the attribute value to 0, which I think is possible in this system if I'm reading the breakdown right. I can clear down to zero in a pretty elegant way, which is one of the thing that excited me about it!
1720937279
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
GiGs said: You roll each die individually, after each die you are asked if you want to roll the next die or add more bonus die or if you want to stop. If you meet the target number you advance, You can't do this with a character sheet. You noted that David M figured out how to do this with ScriptCards - but that needs a Pro subscription. Using the code available to character sheets, you cannot break a single roll into multiple steps like this. You could use Custom Roll Parsing to respond to a single click, and then save the current total in a hidden input and make whatever changes are needed. Then players could click the second die, add it to the total. Then click the third. But you have ti break it down into discrete steps like this - every decision point must be a new action button. You actually can do this with a sheet. There's two ways I can see to do it: First option is to query the user if they want to roll again using a roll query and then send the results to chat only after all the prompts have processed. Benefit here is that the whole roll gets done in a single activation. Downside is that roll queries are a bit intrusive to some people. I do something similar in the storypath sheets. Second option is a little more complicated. Add a chat button to the roll template output that calls a hidden button on the sheet for rolling additional dice. Can use the data passing trick to tell subsequent calls information about the previous calls. This is the basic technique used for the push rolls on free league sheets like alien and blade runner.
1720955111
GiGs
Pro
Sheet Author
API Scripter
Do you have examples of either of those methods?
1720963959
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Yeah, I'll post some code later today when I'm back at my comp.
1720976816
vÍnce
Pro
Sheet Author
Scott C. said: Second option is a little more complicated. Add a chat button to the roll template output that calls a hidden button on the sheet for rolling additional dice. Can use the data passing trick to tell subsequent calls information about the previous calls. This is the basic technique used for the push rolls on free league sheets like alien and blade runner. Sounds like I should implement that for the Forbidden Lands sheet to handle the push mechanic.  Would love to see a basic example of this if/when you have the time Scott.
1721002298

Edited 1721501425
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Here's the example code. Roll Query method HTML &lt;button type="action" name="act_athletics"&gt;athletics&lt;/button&gt; &lt;input type="checkbox" id="athleticsdie1" value="1" name="attr_athletics_die" class="dietracker1 dietracker"/&gt; &lt;input type="checkbox" id="athleticsdie2" value="2" name="attr_athletics_die" class="dietracker2 dietracker" checked/&gt; &lt;input type="checkbox" id="athleticsdie3" value="3" name="attr_athletics_die" class="dietracker3 dietracker"/&gt; This is just a stripped down version of the html that you are using Trooper. Also, note that you shouldn't use a checkbox with it's value set to 0, you can just remove your 0 checkbox and clicking the checkbox that is already checked will clear all of them. Javascript //Converts an object representation of a roll to a roll template string const assembleRoll = (rollObj,startString = '&amp;{template:default}') =&gt; { return Object.entries(rollObj).reduce((str,[key,val]) =&gt;{ str += `{{${key}=${val}}}`; return str; },startString); } // Add all your applicable skill button names to this array const skills = ['athletics']; // This function is called by our listener to iterate through all the rolls. /** * @param {number} die - The number of dice available * @param {number} [i = 1] - The iteration step we are on. * @param {object} [rollObj = {}] - The object that will represent our final roll template output */ const iterateRoll = async (die,i = 1,rollObj = {}) =&gt; { // Roll the die // The startRoll calls inside iterateRoll will all start with `!`. // This makes them API calls which means they will not show up in chat. // The downside to this method is that you pollute the chat history for // the user. If they hit the up arrow in the chat box several times they // will see each of these messages as options to resend. const dieResult = await startRoll(`!{{die=[[d6]]}}`); finishRoll(dieResult.rollId); const dieVal = dieResult.results.die.result; // Store the result in the rollObj rollObj[`die-${i}`] = `[[${dieVal}[computed value]]]`; const remainingDice = die - 1; // If there are dice remaining, then query the user if they want to roll again if(remainingDice){ // Ask the user if they want to keep rolling. You can add additional // information to this query as needed for your system (e.g. the total // rolled so far or number of dice remaining). // Note that we set this as a boolean with no being `0` and yes being `1` const query = await startRoll(`!{{query=[[?{You rolled a ${dieVal} would you like to roll again?|No,0|Yes,1}]]}}`); finishRoll(query.rollId); const queryResponse = query.results.query.result; // If they want to roll again, call iterateRoll to do the processa again if(queryResponse){ return iterateRoll(remainingDice,i+1,rollObj); }else{ // Otherwise return the created rollObj return [rollObj,remainingDice]; } }else{ // We have to use a pseudo roll here because we are awaiting something in the other branch of the if. If both branches of the if/else don't await, the sheet loses it's connection. const pseudo = await startRoll('!'); finishRoll(pseudo.rollId); return [rollObj,remainingDice]; } }; // For each skill, create the listener skills.forEach(skill =&gt; { on(`clicked:${skill}`,() =&gt; { // Get the number of dice available getAttrs([`${skill}_die`],async (attributes) =&gt; { // Start iterating over the dice rolls and asking the user if they want to roll again const [rollObj,remainingDice] = await iterateRoll(+attributes[`${skill}_die`]); // Get the roll template representation of our roll object const rollString = assembleRoll(rollObj); // Send the chat content that the user will actually see const trueRoll = await startRoll(rollString); finishRoll(trueRoll.rollId); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Set the dice remaining for the skill to the new value setAttrs({ [`${skill}_die`]: remainingDice }) }); }); }); So, we iteratively ask the user if they want to roll again until they say no or they've used all their dice. Note that we are using the async/await implementation of startRoll because it allows us to do actual logic in response to the roll result and have divergent logical outcomes with more ease than doing the callback implementation. Button in Roll Template method html edit: fixed a typo in the roll again button setup. &lt;button type="action" name="act_additonal-roll" style="display: none;"&gt;&lt;/button&gt; &lt;button type="action" name="act_athletics"&gt;athletics&lt;/button&gt; &lt;input type="checkbox" id="athleticsdie1" value="1" name="attr_athletics_die" class="dietracker1 dietracker"/&gt; &lt;input type="checkbox" id="athleticsdie2" value="2" name="attr_athletics_die" class="dietracker2 dietracker" checked/&gt; &lt;input type="checkbox" id="athleticsdie3" value="3" name="attr_athletics_die" class="dietracker3 dietracker"/&gt; &lt;rolltemplate class="sheet-rolltemplate-pushable"&gt; &lt;div class="roll"&gt; &lt;span&gt;{{character_name}}&lt;/span&gt; {{#roll-1}}&lt;span&gt;Roll1: {{computed::roll-1}}&lt;/span&gt;{{/roll-1}} {{#roll-2}}&lt;span&gt;Roll1: {{computed::roll-2}}&lt;/span&gt;{{/roll-2}} {{#roll-3}}&lt;span&gt;Roll1: {{computed::roll-3}}&lt;/span&gt;{{/roll-3}} {{#pushData}}&lt;span&gt;[Roll Again](~{{character_name}}|additional-roll||{{computed::pushData}})&lt;/span&gt;{{/pushData}} &lt;/div&gt; &lt;/rolltemplate&gt; There are two additions here. The hidden addtional-roll button, and the rolltemplate. The button method creates the roll method in the roll template itself and uses the character name that is passed to the roll template and a computed pushData property to assemble the button. The important line here is the pushData &nbsp;line. This is what creates the button to call the iterative rolls. Javascript // Add all your applicable skill button names to this array const skills = ['athletics']; // RE Object taken from the K-scaffold // It allows us to encode/decode data so that it can be passed through chat button calls // <a href="https://github.com/Kurohyou-Studios/k-scaffold/blob/9c68202f96a0546e4aeb07df99c259d295fba864/lib/scripts/utility.js#L269" rel="nofollow">https://github.com/Kurohyou-Studios/k-scaffold/blob/9c68202f96a0546e4aeb07df99c259d295fba864/lib/scripts/utility.js#L269</a> const RE = { chars: { '"': '%quot;', ',': '%comma;', ':': '%colon;', '}': '%rcub;', '{': '%lcub;', }, escape(data) { return typeof data === 'object' ? `KDATA${btoa(JSON.stringify(data))}` : `KSTRING${btoa(data)}`; }, unescape(string) { const isData = typeof string === 'string' &amp;&amp; ( string.startsWith('KDATA') || string.startsWith('KSTRING') ); return isData ? ( string.startsWith('KDATA') ? JSON.parse(atob(string.replace(/^KDATA/,''))) : atob(string.replace(/^KSTRING/,'')) ) : string; } }; /** * Assembles and encodes the push data. * @param {object} results - The results details * @param {string} skillName - The name of the skill being rolled */ const assemblePushData = (results,skillName) =&gt; { const pushObj = Object.entries(results).reduce((memo,[key,val]) =&gt; { if(key !== 'pushData'){ memo[key] = val.result; } return memo; },{skill:skillName}); // Calling the RE object described above const pushString = RE.escape(pushObj); return pushString; }; // Rolls the indicated skill const rollSkill = (skill,i=1,rollObj = {}) =&gt; { getAttrs([`${skill}_die`,'character_name'],async attributes =&gt; { const diceNum = +attributes[`${skill}_die`]; const newDice = diceNum - 1; rollObj[`die-${i}`] = '[[d6]]'; if(newDice){ // If there are dice still available, then we can present the push button to the user in the output rollObj.pushData = '[[0[computed value]]]'; } const rollString = assembleRoll(rollObj); const roll = await startRoll(rollString); const computeObj = {}; if(newDice){ computeObj.pushData = assemblePushData(roll.results,skill); } finishRoll(roll.rollId,computeObj); // Set the die number setAttrs({ [`${skill}_die`]: newDice }) }) } skills.forEach(skill =&gt; { on(`clicked:${skill}`,() =&gt; rollSkill(skill)) }); // Listener for the additional die button on('clicked:additional-roll',(event) =&gt; { // parse the information about the previous roll const rollInfo = RE.unescape(event.originalRollId); const nextDie = [1,2,3].find(n =&gt; !rollInfo.hasOwnProperty(`die-${n}`)); const skill = rollInfo.skill; // Create the base roll object to propagate the previous rolls to the new roll const rollObj = [1,2,3].reduce((o,n) =&gt; { if(rollInfo.hasOwnProperty(`die-${n}`)){ o[`die-${n}`] = `[[${rollInfo[`die-${n}`]}[computed value]]]`; } return o; },{}); // Roll the skill rollSkill(skill,nextDie,rollObj) }) //Converts an object representation of a roll to a roll template string const assembleRoll = (rollObj,startString = '&amp;{template:pushable} {{character_name=@{character_name}}}') =&gt; { return Object.entries(rollObj).reduce((str,[key,val]) =&gt;{ str += `{{${key}=${val}}}`; return str; },startString); } This method gets a little complex. It also has a large downside in that if the user has entered certain characters in their character's name, it will fail (e.g. parentheses, brackets, etc).
1721002728

Edited 1721002810
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
And, here's some gifs of these two methods in action. Query method Push Button Method