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

Error: Firebase.update failed: First argument contains NaN in property 'bar2_value'

I've recently started using TokenMod and a new script to change all the tokens in a new game to set the token bars to my preferred attributes but I'm getting an error because some monsters have NaN in their passive_wisdom (that's my guess on why it crashes as that is what bar 2 value is set to). I can use " !fix-bars" to change individual tokens that don't give this error but I'm unable to use " !fix-bars --all" to change 5,000 or so tokens all at once. How can I fix this? Any help would be appreciated! Here's the code I'm using: on('ready',()=>{   const attr = (c,n) => (findObjs({ type: 'attribute', characterid: c.id, name: n })[0]||{get:()=>0});   let characters = {};   const fixToken = (t) => {     let c = getObj('character',t.get('represents'));     if(c){       let npc = (attr(c,'npc').get('current')>0);       let ac = attr(c, (npc ? 'npc_ac' : 'ac'));       let wis = attr(c,'wisdom_mod');       let per_skill = attr(c,'perception_prof');       let per_base = attr(c,'npc_perception_base');       let senses = (`${attr(c,'npc_senses').get('current')}`.match(/passive\s+perception\s+(\d+)/i)||[0,0])[1];       let hp = attr(c,'hp');       let pb = attr(c,'pb');       let pw = attr(c,'passive_wisdom');       let passivePerception = 10 + (npc ? parseInt(per_base.get('current')) : (parseInt(wis.get('current')) + parseInt(((`${per_skill.get('current')}`.length>0) ? pb.get('current') : 0))));       t.set({         bar1_link: ( npc ? '' : (ac ? ac.id : 'sheetattr_ac' )),         bar2_link: (npc ? '' : (pw ? pw.id : 'sheetattr_passive_wisdom')),         bar3_link: ( npc ? '' : hp.id),         bar1_value: ac.get('current'),         bar2_value: (parseInt(senses) || passivePerception),         bar3_value: (npc ? hp.get('max') : hp.get('current')),         bar1_max: '',         bar2_max: '',         bar3_max: hp.get('max')       });       let p = getObj('page',t.get('pageid'));       let scale = parseFloat(p.get('snapping_increment'));       if( !characters.hasOwnProperty(c.id) && c.get('name') === t.get('name')) {         let w = parseInt(t.get('width'));         let h = parseInt(t.get('height'));         if(scale<1){           let mod = 1/scale;           t.set({             height: Math.floor(h*mod),             width: Math.floor(w*mod)           });         }         setDefaultTokenForCharacter(c,t);         if(scale<1){           t.set({             height: h,             width: w           });         }         characters[c.id] = true;       }     }   };   on('chat:message', (msg) => {     if('api'===msg.type && /^!fix-bars\b/i.test(msg.content) && playerIsGM(msg.playerid)) {       let args = msg.content.split(/\s+/).map(s => s.toLowerCase());       let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');       let workQueue = {};       if(args.includes('--all')){         workQueue = findObjs({type:'graphic'});       } else if(msg.selected){         workQueue = msg.selected           .map(o=>getObj('graphic',o._id))           .filter(g=>undefined !== g)           ;       } else {         sendChat('Fix Bars', `/w "${who}" <div>Select one or more tokens to fix bar mappings on, or use <code>!fix-bars --all</code> to fix bar mappings on all tokens in the game.</div>`);         return;       }       let count = 0;       let timeMark = Date.now();       const drainQueue = ()=>{         let t = workQueue.shift();         if(t){           fixToken(t);           ++count;           if((Date.now()-timeMark)>5000){             timeMark = Date.now();             sendChat('Fixing Tokens',`/w gm ...${count}`);           }           setTimeout(drainQueue,0);         } else {           characters = {};           sendChat('Fixing Tokens',`/w gm Finished Fixing Tokens`);         }       };       sendChat('Fixing Tokens',`/w gm Fixing ${workQueue.length} Tokens`);       drainQueue();     }   }); });
1740148823
timmaugh
Forum Champion
API Scripter
Hey, Philip... are you the one that coded that? (Just wondering what your comfort is with javascript and making changes to it) I'm getting an error because some monsters have NaN in their passive_wisdom (that's my guess on why it crashes as that is what bar 2 value is set to). I'm not sure about this. You set the linkage for bar2 to be the passive_wisdom, but you set the bar2_value (what is actually throwing the error) to be the result of your passivePerception equation. parseInt() will give you a NaN value if you apply it to an empty string or to a string that isn't numeric. So I suggest introducing a function like this into your code: const locParseInt = (v, d = 0) => {   let v1 = parseInt(v);   return isNaN(v1) ? d : v1;  }; Then you can just swap that function in to your equation: let passivePerception = 10 + (npc     ? locParseInt(per_base.get('current'))     : (locParseInt(wis.get('current')) + locParseInt(((`${per_skill.get('current')}`.length > 0)         ? pb.get('current')         : 0))     ) ); That should get you past the NaN problems. It might even let you simplify the ternary check against the length of  per_skill.get('current')  to where you just feed the locParseInt function  pb.get('current')  and trust it to return 0 if there is nothing there. (I'm not sure of what your rules are, so it could be that there would be reason for there to be something in this field that you didn't want to use *unless* there was something in the per_skill attribute... in which case, ignore this part.)
Thank you for your response! I am actually not very good at coding at all. I was given this code in a post in the forum. is there a specific place I need to place this ↓  code or can I just add it to the end? const locParseInt = (v, d = 0) => {   let v1 = parseInt(v);   return isNaN(v1) ? d : v1;  }; Do I put this ↓   in there along with the other as well? I'm not sure where they fit in let passivePerception = 10 + (npc     ? locParseInt(per_base.get('current'))     : (locParseInt(wis.get('current')) + locParseInt(((`${per_skill.get('current')}`.length > 0)         ? pb.get('current')         : 0))     ) ); If there was no passive_wisdom attribute on the character, I just wanted it to be blank.
1740235283
timmaugh
Forum Champion
API Scripter
I believe the final form would look like this: on('ready',()=>{   const attr = (c,n) => (findObjs({ type: 'attribute', characterid: c.id, name: n })[0] || {get:()=>0});   let characters = {};   const locParseInt = (v, d = 0) => {     let v1 = parseInt(v);     return isNaN(v1) ? d : v1;    };       const fixToken = (t) => {     let c = getObj('character',t.get('represents'));     if(c){       let npc = (attr(c,'npc').get('current')>0);       let ac = attr(c, (npc ? 'npc_ac' : 'ac'));       let wis = attr(c,'wisdom_mod');       let per_skill = attr(c,'perception_prof');       let per_base = attr(c,'npc_perception_base');       let senses = (`${attr(c,'npc_senses').get('current')}`.match(/passive\s+perception\s+(\d+)/i)||[0,0])[1];       let hp = attr(c,'hp');       let pb = attr(c,'pb');       let pw = attr(c,'passive_wisdom');       let passivePerception = 10 + (npc ? locParseInt(per_base.get('current')) : (locParseInt(wis.get('current')) + locParseInt(((`${per_skill.get('current')}`.length>0) ? pb.get('current') : 0))));       t.set({         bar1_link: ( npc ? '' : (ac ? ac.id : 'sheetattr_ac' )),         bar2_link: (npc ? '' : (pw ? pw.id : 'sheetattr_passive_wisdom')),         bar3_link: ( npc ? '' : hp.id),         bar1_value: ac.get('current'),         bar2_value: (parseInt(senses) || passivePerception),         bar3_value: (npc ? hp.get('max') : hp.get('current')),         bar1_max: '',         bar2_max: '',         bar3_max: hp.get('max')       });       let p = getObj('page',t.get('pageid'));       let scale = parseFloat(p.get('snapping_increment'));       if( !characters.hasOwnProperty(c.id) && c.get('name') === t.get('name')) {         let w = parseInt(t.get('width'));         let h = parseInt(t.get('height'));         if(scale<1){           let mod = 1/scale;           t.set({             height: Math.floor(h*mod),             width: Math.floor(w*mod)           });         }         setDefaultTokenForCharacter(c,t);         if(scale<1){           t.set({             height: h,             width: w           });         }         characters[c.id] = true;       }     }   };   on('chat:message', (msg) => {     if('api'===msg.type && /^!fix-bars\b/i.test(msg.content) && playerIsGM(msg.playerid)) {       let args = msg.content.split(/\s+/).map(s => s.toLowerCase());       let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');       let workQueue = {};       if(args.includes('--all')){         workQueue = findObjs({type:'graphic'});       } else if(msg.selected){         workQueue = msg.selected           .map(o=>getObj('graphic',o._id))           .filter(g=>undefined !== g);       } else {         sendChat('Fix Bars', `/w "${who}" <div>Select one or more tokens to fix bar mappings on, or use <code>!fix-bars --all</code> to fix bar mappings on all tokens in the game.</div>`);         return;       }       let count = 0;       let timeMark = Date.now();       const drainQueue = ()=>{         let t = workQueue.shift();         if(t){           fixToken(t);           ++count;           if((Date.now()-timeMark)>5000){             timeMark = Date.now();             sendChat('Fixing Tokens',`/w gm ...${count}`);           }           setTimeout(drainQueue,0);         } else {           characters = {};           sendChat('Fixing Tokens',`/w gm Finished Fixing Tokens`);         }       };       sendChat('Fixing Tokens',`/w gm Fixing ${workQueue.length} Tokens`);       drainQueue();     }   }); }); I can't speak to the rest of the code because I was limiting myself to the cause of the NaN error. For instance, if the bar2 linkage and value are working, otherwise, great. If they are reporting incorrectly, that would be something to look at again.
hmmm it appears its still not working. its giving me the following error: For reference, the error message generated was:  Error: Firebase.update failed: First argument contains undefined in property 'bar3_link' Error: Firebase.update failed: First argument contains undefined in property 'bar3_link' at Ba (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:9:49) at Ba (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:207) at Aa (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:8:462) at Ea (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:403) at J.update (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:146:318) at TrackedObj._doSave (/home/node/d20-api-server/api.js:998:27) at Timeout.later [as _onTimeout] (/home/node/d20-api-server/node_modules/underscore/underscore-node-f.cjs:1146:37) at listOnTimeout (node:internal/timers:569:17) at process.processTimers (node:internal/timers:512:7)
1740408594
timmaugh
Forum Champion
API Scripter
OK, so that is where it is writing the ID of the hp attribute to the bar3 link field. In other words, this is where the token is NOT an NPC, so you are trying to use the hp attribute. From the error you're getting, you are unable to find a property on that object, which means that your initial retrieval of the hp attribute didn't work. The attribute is retrieved by this line: let hp = attr(c, 'hp'); ...which invokes this function: const attr = (c, n) => (findObjs({ type: 'attribute', characterid: c.id, name: n })[0] || { get: () => 0 }); If you can't read that, it's basically looking for an attribute named <<whatever you pass in for "n">> that belongs to the character you pass in as "c". If it CAN'T find an attribute to satisfy those requirements, it will use a dummy/default attribute object with a "get()" function that is hard-coded to just return 0. IDs are assigned to every Roll20 object, so that means if that hp attribute exists it should have an id. Since you can't read the ID, it means your hp retrieval is failing (the code isn't finding an attribute to match the parameters). When that happens, it kicks over to using the default object... but that one is created without an "id" property, so you get "undefined" when you try to access it and assign to the bar3 link. In other words, we can approach it in a few ways: 1) we can give the default object a default ID property (like an empty string) so that nothing is set in the bar3 link. But this doesn't tell us why, if we have a character, we don't have an hp attribute. 2) It could be that the hp attribute is actually named something different (like HP). I think this would be rare, but if we suspect this we can change the attr() function so that instead of using the name as a parameter, we build a regular expression out of it and test with case insensitivity. Maybe that returns the proper attribute. 3) It could be that the hp attribute has never been initialized, so the API can't access it (if it never receives a value from the sheet, it can be like it doesn't exist). In this case, we can test for it, report the problem for the problem characters, and skip over setting them.  IMO, number 1 will "fix" it so that you don't get the error, but you may never know which tokens/characters are affected by this. I double 2 is coming into play, but we can keep it in our back pocket if other approaches don't pay dividends. I say 3 is the most likely culprit. For now, lets add the id property to the default object returned by the attr() function, but let's also trap for that and report the character out to chat so you can investigate. This should let you apply all of your settings to all the tokens, but for the ones that get reported, you might need to investigate/correct their HP, then relink those bars. The fix would be in 2 parts. First, locate this line near the top of the script:     const attr = (c, n) => (findObjs({ type: 'attribute', characterid: c.id, name: n })[0] || { get: () => 0 }); ... and change it to...     const attr = (c, n) => (findObjs({ type: 'attribute', characterid: c.id, name: n })[0] || { id: '', get: () => 0 }); Then, about 22 lines into the script, find this line:             t.set({ Right before that line, insert this:             if(hp.id === '') {               sendChat('Fixing Tokens', `/w gm &{template:default}} {{name=Bad HP}} {{Character=${c.get('name')}}} {{Token Data}} {{Page=${getObj('page',t.get('pageid')).get('name')}}} {{Location=(${t.get('left')}, ${t.get('top')})}}`);             } You might get duplicate reports for a character if they are represented by tokens on different pages (or multiple tokens on the same page). But this should give you an idea of which characters you have to look at.
Is this for D&D 5e 2014 by roll20 npc sheets? These don't have current HP set, nor an editable field for it on the sheet - only max. Might that be causing the issue?
Thank you! The script you gave me works really well for identifying the problem tokens. It definitely seems that any tokens that have no hp attribute (or are blank and not assigned to any characters) are the problem preventing my script from working. The only problem is: once I run your script and delete the problem tokens, my script works without kicking back an error and claims that it fixed all the tokens bars, but when I drag a monster or npc token from their sheet, the token is the same as before. I deleted your script and added my script back in, and now it won't work even though there are no problem tokens anymore. My 2014 D&D 5e monster library used to have the blue bar (bar 2) set to speed, which was mostly not very helpful so I decided to change them all to show passive_perception instead. But after using the script, they're all still set to their speed attribute. I am able to click on individual tokens and change them with the " !fix-bars" macro but the " !fix-bars  --all " macro no longer works. I'm not sure what to do.
1740683471
timmaugh
Forum Champion
API Scripter
It sounds like this is a matter of updating the default token. You can make the change to a token, but until you write that token to be the "default token" of the sheet, your change is just local/temporary. You said you have TokenMod installed, which will let you do this via script. TokenMod will update all selected tokens to be the default token on their respective sheets (skipping the ones that aren't associated with a particular sheet) using this command: !token-mod --set defaulttoken So you can either manually select everything on a given map and run the above command, orrrrrrrrrrr....  ... you can install the MetascriptToolbox and change that command slightly to be: !token-mod --set defaulttoken {&select *} That will automatically select (virtually) all of the tokens on the current page for the purposes of the message. In other words, although your interface will not change to show that you have all tokens selected, by the time TokenMod catches the command line, it will look to TokenMod as if that's exactly what you did. Something to note... since this works from the direction of the token back to the sheet, you must have a representative token somewhere on some page where you run this command for the sheet to be updated. It won't fix the "Black Crier" character's default token if you don't have a "Black Crier" token on a page where you run one of the above commands.