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 .
×

roll-button javascript action does not work

Hi, the following button in the character sheet worked well: <button type="roll" name="roll_gezsp" value="@{abilityroll} wirkt einen geziehlten Zauber mit dem Ergebnis [[1d10! + @{gezsp_attribut} + @{gezsp} + @{gezsp_bonus} + @{global_modifier} + ?{Modifier?|0}]]"></button> after changing it to: <button type="roll" name="act_rollgezsp"></button> [...] <script type="text/worker">   function build_gezieltesprueche_rollcommand(anzahl_ziele,malus) {     var value="@{abilityroll} wirkt einen geziehlten Zauber auf "+anzahl_ziele.toString()+" Ziele mit dem Ergebnis:";     malus *= anzahl_ziele-1;     var roll="[[1d10! + @{gezsp_attribut} + @{gezsp} + @{gezsp_bonus} + @{global_modifier} + ?{Modifier?|0} - "+malus.toString()+"]]"     while( anzahl_ziele > 0 ) {       value += " " + roll;       anzahl_ziele--;     }     return value;   }   on("clicked:rollgezsp", function() {     getAttrs(["character_name"], function (values) {       let anzahl_ziele = parseInt( prompt("Anzahl Ziele?", "1") ) || 0;       let character_name = values.character_name || "";       sendChat(character_name,build_gezieltesprueche_rollcommand(anzahl_ziele,2));     });   }); </script> It does no longer work. :-( Is there a simple syntax error, I do not see. Or is it a design issue and I am using commands/call/methods not allowed to use (or the wrong way)? Any hint?
1661165849

Edited 1661166759
Oosh
Sheet Author
API Scripter
You still have it set to a roll button. Here's a run-down of the different types - you want type="action" for an action button. Edit - I didn't read the code very carefully, there are some other issues - you can't call window.prompt() from the character sheet sandbox, and sendChat is an API method, not character sheet. You'll need to either leave it as a roll button (the original) or move the whole thing over to an action button with Custom Roll Parsing . What problem were you trying to solve by changing to an action button? You don't need getAttrs to grab a character name, you can just use @{character_name} in the roll button string.
I have two dependencies from the amount of targets (anzahl_ziele): amount of rolls (one roll per target) each roll gets a malus multiplied by the amount of tragets The malus calculation was possible within the roll-syntax "[[ ... ]]". But the dynamic amount of rolls, I found out that the roll chat syntax can not do that. So I tried to implement it in javascript: while( anzahl_ziele > 0 ) { value += " " + roll;   anzahl_ziele--; } If there is an alternative way instead of JavaScript, I would be pleased.
1661216784
Oosh
Sheet Author
API Scripter
Ahhhh ok. I think you're right - as far as I know there's no way to handle that with normal roll buttons. If the number of targets will always be a low number, like 1, 2, 3 or 4, I think you could fake it with a roll button, but to do it properly will need Custom Roll Parsing. So the button is the easy part, and would look like this: <button type=" action " name="act_rollgezsp"></button> The handler is going to be a bit more difficult. I haven't written anything sheet related for a while, someone else might have a more up-to-date way to handle this, but here's how I would look at it. First, you need to use a trick to grab player input before you do your real roll. Trick #1 in t his thread has an explanation. This replaces your call to window.prompt(). The next thing is replacing sendChat() (only works from the API script sandbox) with startRoll() (specifically for Custom Roll Parsing). The roll function will also need to be asynchronous to handle having the startRoll() inside a loop: // Helper function to grab player input const getQuery = async (queryText) => { const rxGrab = /^0\[(.*)\]\s*$/; const rollBase = `! {{query1=[[ 0[${queryText}] ]]}}`, // just a [[0]] roll with an inline tag queryRoll = await startRoll(rollBase), queryResponse = (queryRoll.results.query1.expression.match(rxGrab) || [])[1]; finishRoll(queryRoll.rollId); return queryResponse; }; // Async timeout function to control the loop speed const timeout = async (ms) => new Promise(res => setTimeout(() => res(), ms)); // This becomes an async function to handle the asynchronous startRoll() calls async function build_gezieltesprueche_rollcommand(malus) { // Get number of targets from player let anzahl_ziele = await getQuery(`?{Anzahl Ziele?|1}`); // Set up roll string malus *= anzahl_ziele-1; const rollString = `@{abilityroll} wirkt einen geziehlten Zauber auf ${anzahl_ziele} Ziele mit dem Ergebnis: [[1d10! + @{gezsp_attribut} + @{gezsp} + @{gezsp_bonus} + @{global_modifier} + ?{Modifier?|0} - ${malus}]]`; while( anzahl_ziele > 0 ) { const roll = await startRoll(rollString); finishRoll(roll.rollId); await timeout(500); // You might want to make this shorter or longer? Or remove it? anzahl_ziele--; } } on("clicked:rollgezsp", function() { build_gezieltesprueche_rollcommand(2) }); I haven't tested any of that, but it should work I think. I just changed the roll string to use Template Literal syntax as it tends to be a bit easier to read long Roll20 macro strings. The other change is the 'await timeout' call inside the loop - you might want to adjust this number (milliseconds) to provide a bit of a buffer between rolls.
1661235060

Edited 1661235100
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
I'll note that the timeout bit won't work. The only async bit of JS we can use is startRoll; setTimeout will break the connection between the character sheet backend and the active character sheet. For the dynamic rolls, if you don't need to do any manipulation of the values that they display, I'd just set up your roll template with an allProps()  handle, and then just send them to chat all at once. Something like this: // Helper function to grab player input const getQuery = async (queryText) => { const rxGrab = /^0\[(.*)\]\s*$/; const rollBase = `! {{query1=[[ 0[${queryText}] ]]}}`, // just a [[0]] roll with an inline tag queryRoll = await startRoll(rollBase), queryResponse = (queryRoll.results.query1.expression.match(rxGrab) || [])[1]; finishRoll(queryRoll.rollId); return queryResponse; }; // This becomes an async function to handle the asynchronous startRoll() calls async function build_gezieltesprueche_rollcommand(malus) { // Get number of targets from player and convert to a number let anzahl_ziele = +(await getQuery(`?{Anzahl Ziele?|1}`)); // Set up roll string malus *= anzahl_ziele - 1; //Make a roll field for each of the targets const targetRolls = [...Array(anzahl_ziele).keys()] .map(i => `{{Ziele ${i + 1}=[[1d10! + @{gezsp_attribut} + @{gezsp} + @{gezsp_bonus} + @{global_modifier} + ?{Modifier?|0} - ${malus}]]}}`).join(' '); const rollString = `&{template:anzahl_template} {{description=@{abilityroll} wirkt einen geziehlten Zauber auf ${anzahl_ziele} Ziele mit dem Ergebnis}} ${targetRolls}`; const roll = await startRoll(rollString); finishRoll(roll.rollId); } on("clicked:rollgezsp", function() { build_gezieltesprueche_rollcommand(2) }); Your roll template definition will look something like this: <rolltemplate class="sheet-rolltemplate-anzahl_template"> <span>{{description}}</span> {{#allProps()}} <div class="sheet-key">{{key}}</div> <div class="sheet-value">{{value}}</div> {{/allProps()}} </rolltemplate>
1661254524
Oosh
Sheet Author
API Scripter
Ahhhhh, thanks Scott. I'd thought it was only the async getAttrs and setAttrs that broke the furniture, but was obviously mistaken. Also worth adding in - some kind of sane maximum for the number of targets - if a player accidentally types "99" into the target prompt, you probably don't want 99 attack rolls spamming chat for everyone else.