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 Character Sheet - Damage on Critical and Extreme Success reusing XdY information

On the Call of Cthulhu 7th Edition sheet when you roll a critical or extreme success it show the Extreme Damage as the maximum of the die roll plus any extra, so a 2d6 +4 = 12 + 4 = 16. You don't have to store what the extreme damage is in a field on the sheet it just seems to compute it from the Damage field for the Weapon. Can this be done a custom character sheet as well?  I have a roll template that determines the critical/extreme successes as well as show/hide an extra damage field on the critical/extreme success, but didn't see a way to reuse the damage die to compute the maximum damage. I may have missed an easy trick, and if so, apologies in advance.
1745775314
Maïlare
Pro
Sheet Author
API Scripter
Hello, to do this on a custom sheet, you have to use javascript to make a sheet worker script. You can find details here : <a href="https://wiki.roll20.net/Building_Character_Sheets#JavaScript_2" rel="nofollow">https://wiki.roll20.net/Building_Character_Sheets#JavaScript_2</a>
Thank you, ended up creating an API script to handle the extreme/critical parsing and pass back the values wanted. Appreciate it!
1745775834

Edited 1745775863
Maïlare
Pro
Sheet Author
API Scripter
It's my opinion but I think sheet workers are better to do simple operations like this, because it's calculate directly in the sheet. But API's mod is good too. :D
1745794734
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Yep, I'd agree. Doing this via sheetworker should be the preferred option. There's less interference this way (e.g. the api server might crash) and you can modify the roll output itself to show the needed info.
1745805266
GiGs
Pro
Sheet Author
API Scripter
If you choose to switch to a sheet worker, I have a guide to Custom Roll Parsing, the sheet worker method you'd use for this, here: <a href="https://cybersphere.me/guide-to-custom-roll-parsing/" rel="nofollow">https://cybersphere.me/guide-to-custom-roll-parsing/</a>
Thank you all for the responses, I went back and reworked it as a sheet worker&gt; Sheet worker: // Common parser function function parseDamageString(damageString, prefix) { const updates = {}; const match = damageString.match(/^(\d+)[dD](\d+)([+\-]\d+)?$/); if (match) { const numDice = parseInt(match[1], 10) || 0; const diceSize = parseInt(match[2], 10) || 0; const bonus = parseInt(match[3] || "0", 10) || 0; updates[`${prefix}_damage_numdice`] = numDice; updates[`${prefix}_damage_dicesize`] = diceSize; updates[`${prefix}_damage_bonus`] = bonus; } else { updates[`${prefix}_damage_numdice`] = 0; updates[`${prefix}_damage_dicesize`] = 0; updates[`${prefix}_damage_bonus`] = 0; } return updates; } // Auto-calculate damage components for fixed weapon (weapon1_mdr) on("sheet:opened add:weapon1_mdr_damage change:weapon1_mdr_damage", function() { getAttrs(["weapon1_mdr_damage"], function(values) { const damageString = values.weapon1_mdr_damage || ""; const updates = parseDamageString(damageString, "weapon1_mdr"); setAttrs(updates); }); }); // Auto-calculate damage components for repeating weapons (repeating_weaponsmdr) - change only on("add:repeating_weaponsmdr change:repeating_weaponsmdr:weapondamage_mdr", function(eventInfo) { const repeatingId = eventInfo.sourceAttribute.match(/repeating_weaponsmdr_([^_]+)_/)[1]; getAttrs([`repeating_weaponsmdr_${repeatingId}_weapondamage_mdr`], function(values) { const damageString = values[`repeating_weaponsmdr_${repeatingId}_weapondamage_mdr`] || ""; const updates = parseDamageString(damageString, `repeating_weaponsmdr_${repeatingId}`); setAttrs(updates); }); }); // On sheet:opened, manually get all repeating rows on("sheet:opened", function() { getSectionIDs("repeating_weaponsmdr", function(ids) { if (ids.length === 0) return; const fields = ids.map(id =&gt; `repeating_weaponsmdr_${id}_weapondamage_mdr`); getAttrs(fields, function(values) { const updates = {}; ids.forEach(id =&gt; { const damageString = values[`repeating_weaponsmdr_${id}_weapondamage_mdr`] || ""; Object.assign(updates, parseDamageString(damageString, `repeating_weaponsmdr_${id}`)); }); if (Object.keys(updates).length &gt; 0) { setAttrs(updates); } }); }); }); Hidden inputs for static weapon: &lt;input type="hidden" name="attr_weapon1_mdr_damage_numdice" value="0" /&gt; &lt;input type="hidden" name="attr_weapon1_mdr_damage_dicesize" value="0" /&gt; &lt;input type="hidden" name="attr_weapon1_mdr_damage_bonus" value="0" /&gt; &lt;input type="hidden" name="attr_weapon1_mdr_strain" value="0" /&gt; Static Weapon Roll: &lt;button class='new-roll' type='roll' value='&amp;{template:coc-attack-1} {{name=@{weapon1_mdr_name}}} {{modifier=@{dice_modifier_checkbox}}} {{success=[[@{weapon1_skill_mdr}+@{dice_modifier_checkbox}]]}} {{hard=[[floor((@{weapon1_skill_mdr} + @{dice_modifier_checkbox})/2)]]}} {{extreme=[[floor((@{weapon1_skill_mdr} + @{dice_modifier_checkbox})/5)]]}} {{malf=@{weapon1_mdr_malf}}} {{roll1=[[1d100]]}} {{damage=[[@{weapon1_mdr_damage}@{weapon1_mdr_db}]]}} {{extremedamage=[[(@{weapon1_mdr_damage_numdice}*@{weapon1_mdr_damage_dicesize})+@{weapon1_mdr_damage_bonus}]]}} {{strain=[[ @{weapon1_mdr_strain} + 0 ]]}}' name='roll_weapon1_mdr_attack' /&gt; Hidden Inputs for repeating weapon: &lt;input type="hidden" name="attr_damage_numdice" value="0" /&gt; &lt;input type="hidden" name="attr_damage_dicesize" value="0" /&gt; &lt;input type="hidden" name="attr_damage_bonus" value="0" /&gt; &lt;input type="hidden" name="attr_weaponstrain_mdr" value="0" /&gt; Repeating Weapon Roll: &lt;button class='new-roll' type='roll' value='&amp;{template:coc-attack-1} {{name=@{weaponname_mdr}}} {{modifier=@{dice_modifier_checkbox}}} {{success=[[@{weaponskill_mdr}+@{dice_modifier_checkbox}]]}} {{hard=[[floor((@{weaponskill_mdr}+@{dice_modifier_checkbox})/2)]]}} {{extreme=[[floor((@{weaponskill_mdr}+@{dice_modifier_checkbox})/5)]]}} {{malf=@{weaponmalf_mdr}}} {{roll1=[[1d100]]}} {{damage=[[@{weapondamage_mdr}@{weapondb_mdr}]]}} {{extremedamage=[[(@{damage_numdice}*@{damage_dicesize})+@{damage_bonus}]]}} {{strain=[[ @{weaponstrain_mdr} + 0 ]]}}' name='roll_weaponskill_mdr_check' /&gt; Roll Template: &lt;!-- &amp;{template:coc-attack-1} --------------------------------------------------------------------------------------------------------- --&gt; &lt;rolltemplate class="sheet-rolltemplate-coc-attack-1"&gt; &lt;table&gt; &lt;caption&gt;{{name}} | {{modifier}}&lt;/caption&gt; &lt;tr&gt;&lt;td class="template_label" data-i18n="value"&gt;Value:&lt;/td&gt;&lt;td class="template_value"&gt;{{success}}/{{hard}}/{{extreme}}&lt;/td&gt;&lt;/tr&gt; {{#malf}} &lt;tr&gt;&lt;td class="template_label" data-i18n="malf"&gt;Malf:&lt;/td&gt;&lt;td class="template_value"&gt;{{malf}}&lt;/td&gt;&lt;/tr&gt; {{/malf}} &lt;tr&gt;&lt;td class="template_label" data-i18n="rolled"&gt;Rolled:&lt;/td&gt;&lt;td class="template_value"&gt;{{roll1}}&lt;/td&gt;&lt;/tr&gt; &lt;tr style="background:#BEBEBE;"&gt; &lt;td class="template_label" data-i18n="result"&gt;Result:&lt;/td&gt; &lt;!-- 01 is always Critical --&gt; {{#rollTotal() roll1 1}} &lt;td style="background:Lime;" class="template_value"&gt;&lt;b data-i18n="critical"&gt;Critical&lt;/b&gt;&lt;/td&gt; {{/rollTotal() roll1 1}} &lt;!-- Is Success --&gt; {{#rollBetween() roll1 2 success}} &lt;!-- Is Standard --&gt; {{#rollGreater() roll1 hard}} &lt;td style="background:DarkGreen;" class="template_value" data-i18n="success"&gt;Success&lt;/td&gt; {{/rollGreater() roll1 hard}} &lt;!-- Is Hard or Extreme --&gt; {{#rollBetween() roll1 2 hard}} &lt;!-- Is Hard --&gt; {{#rollGreater() roll1 extreme}} &lt;td style="background:Green;" class="template_value" data-i18n="hard"&gt;Hard&lt;/td&gt; {{/rollGreater() roll1 extreme}} &lt;!-- Is Extreme --&gt; {{#rollBetween() roll1 2 extreme}} &lt;td style="background:LightGreen;" class="template_value"&gt;&lt;b data-i18n="extreme"&gt;Extreme&lt;/b&gt;&lt;/td&gt; {{/rollBetween() roll1 2 extreme}} {{/rollBetween() roll1 2 hard}} {{/rollBetween() roll1 2 success}} &lt;!-- Is Not Success --&gt; {{#rollGreater() roll1 success}} &lt;!-- Is skill &gt;= 50 --&gt; {{#rollGreater() success 49}} &lt;!-- Is Fumble --&gt; {{#rollTotal() roll1 100}} &lt;td style="background:Red;" class="template_value"&gt;&lt;b data-i18n="fumble"&gt;Fumble&lt;/b&gt;&lt;/td&gt; {{/rollTotal() roll1 100}} &lt;!-- Is Fail --&gt; {{#rollLess() roll1 100}} &lt;td style="background:Crimson;" class="template_value" data-i18n="fail"&gt;Fail&lt;/td&gt; {{/rollLess() roll1 100}} {{/rollGreater() success 49}} &lt;!-- Is skill &lt; 50 --&gt; {{#rollLess() success 50}} &lt;!-- Is Fumble --&gt; {{#rollGreater() roll1 95}} &lt;td style="background:Red;" class="template_value"&gt;&lt;b data-i18n="fumble"&gt;Fumble&lt;/b&gt;&lt;/td&gt; {{/rollGreater() roll1 95}} &lt;!-- Is Fail --&gt; {{#rollLess() roll1 96}} &lt;td style="background:Crimson;" class="template_value" data-i18n="fail"&gt;Fail&lt;/td&gt; {{/rollLess() roll1 96}} {{/rollLess() success 50}} {{/rollGreater() roll1 success}} &lt;/tr&gt; &lt;tr&gt; &lt;td class="template_label" data-i18n="dam"&gt;Dam:&lt;/td&gt;&lt;td class="template_value"&gt;{{damage}}&lt;/td&gt; &lt;/tr&gt; &lt;!-- Only show Extreme Damage on Critical or Extreme --&gt; {{#rollTotal() roll1 1}} &lt;tr&gt; &lt;td class="template_label" data-i18n="extremedamage"&gt;Extreme Dam:&lt;/td&gt;&lt;td class="template_value"&gt;{{extremedamage}}&lt;/td&gt; &lt;/tr&gt; {{/rollTotal() roll1 1}} {{#rollBetween() roll1 2 extreme}} &lt;tr&gt; &lt;td class="template_label" data-i18n="extreme-damage"&gt;Extreme Dam:&lt;/td&gt;&lt;td class="template_value"&gt;{{extremedamage}}&lt;/td&gt; &lt;/tr&gt; {{/rollBetween() roll1 2 extreme}} {{#^rollTotal() strain 0}} &lt;tr&gt; &lt;td class="template_label" data-i18n="strain"&gt;Strain:&lt;/td&gt;&lt;td class="template_value"&gt;{{strain}}&lt;/td&gt; &lt;/tr&gt; {{/^rollTotal() strain 0}} &lt;/table&gt; &lt;/rolltemplate&gt;
1745810444
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
If you want some code critique, there's several things that I'd adjust here: None of these listeners should fire on sheet opened. With very few exceptions, the only time sheet opened should be used is 1) if something is being done with the jquery implementation since it isn't persistent, or 2) update logic is being run. There is no "add" event in sheetworkers. The change event and repeating row "removed" event handle all attribute events.
Thank you, always appreciate critiques. Will adjust the code and review the wiki for better code.