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

Trouble with NPC spell save DC in Roll20's 5e sheet

Hi all.  I've run into a problem with NPC spell casters in R20.  I have an NPC cleric that I created from scratch.  I dragged the spells from the compendium onto his sheet, and I think I have him configured correctly. But when I roll a hold person, the spell description appears in chat, without the Save DC displayed at the bottom.  Same goes for all other save spells. I've found a few other threads from am few years ago, but none of them seem to have a solution that works even temporarily.  I've tried change the level, removing then dragging and dropping the spell again, etc., all to no avail. Any help?
1636342755
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Try changing the Spellcasting ability from wisdom to something else and back again. Also, are you using the official D&D 5th Edition by Roll20 Sheet, or one of the variations on the older sheet?
Thanks for the swift response.  I changed the spell casting ability to Intelligence; still no save DC displayed.  I did import a mage from the compendium, and it seems to work fine there.  Also, I am using the "D&D 5e by Roll20" sheet.  
1636345166
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Hmm. You could try each of the other possible attribute culprits. I know sometimes there are hidden attributes set by sheetworkers when you manually enter a value.
1636356175

Edited 1636356669
Oosh
Sheet Author
API Scripter
Try changing the "At Higher Levels" text of the spell, save it, then roll it to chat and see if it's fixed. Alternatively, try changing the spell output to Attack, save it, then change it back to Spellcard. These appear to be the only two triggers for the function that inserts {{savedc=@{savedc}}} into the Attribute which holds the roll macro. This should probably be changed so update_spelloutput()  triggers when a spell is added to any repeating row, but nothing I've ever suggested for the sheet got fixed so I won't waste my time reporting it. Someone else is free to :) Edit - I see now there is a sheet update function to update the spell macros with the newer savedc property. But it only runs on PC sheets, for whatever reason. Potentially a design decision for games where the NPC attacks are public, but save DC is the one thing the DM doesn't want public? Seems like a narrow audience for that one, /shrug If it is a design decision, it's a feature not a bug, and probably won't change.
Oosh said: Try changing the "At Higher Levels" text of the spell, save it, then roll it to chat and see if it's fixed. Alternatively, try changing the spell output to Attack, save it, then change it back to Spellcard. Changing the spell to an Attack, and then back to spellcard, seems to have fixed it!  It's not elegant, and it's gonna be a toss up whether it's easier to just put a Spellcasting ability note in the abilities section and just leave it at that.  It's a shame that efforts to get changes made are so.. .  difficult.
1636383574
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
The sheet is pretty much the oldest one there is, and has massive technical debt, having passed through many hands. Thanks, Oosh. I knew something had to be toggled to make that work, but was less than helpful in remembering what it was. Sorry for the wrong steers, Mac.
1636419888

Edited 1636424308
Oosh
Sheet Author
API Scripter
Since you've got Pro, you can use this to fix all spells in the Campaign if you'd like. Run it with !savedcfix on ( 'ready' , () => {   on ( 'chat:message' , ( msg ) => {       if ( /api/ i . test ( msg . type ) && / ^ !savedcfix/ i . test ( msg . content ) && playerIsGM ( msg . playerid )) {           let counter = 0 ;           let npcList = findObjs ({ type : 'attribute' , name : 'npc' })                           . filter ( a => a . get ( 'current' ) == 1 )                           . map ( a => a . get ( 'characterid' ));           let spellList = findObjs ({ type : 'attribute' })               . filter ( a => / ^ repeating_spell. * rollcontent/ . test ( a . get ( 'name' )) && npcList . includes ( a . get ( 'characterid' )));           let spellsNeedFixing = spellList . filter ( a => /template:spell/ i . test ( a . get ( 'current' )) && ! /{{savedc=/ i . test ( a . get ( 'current' )));           spellsNeedFixing . forEach ( a => {               let currentMacro = a . get ( 'current' ),                   newMacro = currentMacro . replace ( '{{innate=@{innate}}}' , '{{innate=@{innate}}} {{savedc=@{spell_save_dc}}}' );               a . set ({ current : newMacro });               counter ++;           });           let chatSummary = ` ${ npcList . length } NPCs found \n ${ spellList . length } NPC spells found \n ${ spellsNeedFixing . length } spells required fixing \n ${ counter } spells modified.` ;           sendChat ( 'saveDCfix' , `/w gm &{template:default} {{name=saveDCfix Summary}} {{= ${ chatSummary } }}` );       }   }); }); That'll find any NPC spells missing the {{savedc}} property and add it in. It uses the same string replacer as the character sheet, so hopefully it should catch all spells. If you're likely to be adding more spells as you go, you can also monitor for changes and auto-apply the fix. Although this has a bunch of very fast conditionals at the start to avoid running any laggy code unless necessary, it's still an extra handler running in the background for each added (not modified) Attribute. I'd say any increased latency would be absolutely unnoticeable since adding new spells is reasonably rare during actual game time, but still, only run it if you can't be bothered keeping up with the manual approach: on ( 'ready' , () => {   on ( 'add:attribute' , ( ev ) => {     if ( / ^ repeating_spell/ i . test ( ev . get ( 'name' ))) {       if ( /rollcontent/ i . test ( ev . get ( 'name' ))) {         let charId = ev . get ( 'characterid' ),             npc = findObjs ({ type : 'attribute' , characterid : charId , name : 'npc' }),             isNpc = npc . length ? npc [ 0 ]. get ( 'current' ) == 1 ? true : false : false ;         if ( isNpc ) {           let actualAttr = getObj ( 'attribute' , ev . id );           setTimeout (() => {             let retries = 4 ;             let awaitWorker = setInterval (() => {                 if ( actualAttr && actualAttr . get ( 'current' ) != '' ) clearInterval ( awaitWorker );                 else {                     retries --;                     if ( retries < 1 ) return ;                 }             }, 1000 );             if (! /template:spell/ i . test ( actualAttr . get ( 'current' ))) return ;             if ( /{{savedc=/ i . test ( actualAttr . get ( 'current' ))) return ;             let newMacro = actualAttr . get ( 'current' ). replace ( '{{innate=@{innate}}}' , '{{innate=@{innate}}} {{savedc=@{spell_save_dc}}}' );             actualAttr . set ({ current : newMacro });             let nameRef = actualAttr . get ( 'name' ). replace ( /rollcontent/ i , 'spellname' );             let char = getObj ( 'character' , actualAttr . get ( 'characterid' )),                 charName = char ? char . get ( 'name' ) : '' ;             sendChat ( 'saveDCbot' , `/w gm &{template:default} {{name=saveDCbot}} {{NPC modded= ${ charName } }} {{Spell added=@{ ${ charName } | ${ nameRef } }}} {{saveDC fix=Applied}}` );           }, 500 );         }       }     }   }); }); By the way, this will fail if it can't find the updated roll macro within 4.5 seconds. The Compendium knows nothing of the 5e sheet macro details and just drops the character-agnostic data, the sheetworkers then assemble to roll macro. If this takes an excessive amount of time to happen, or hangs, the spell won't be modified.