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

Help With This Script to Automatically Add Status Based on Hit Points

I found this script to automatically change the status markers based on HP. However, I am struggling to edit it to make the markers replace each other versus stack. By the time a PC reaches 0 HP they have a yellow, red, black flag AND the death markers on them. I would like to keep replacing the one with the current HP state. Here is the script: var CONFIG = [ {barId: 1, barRatio: .75, status: "yellow", whenLow: true}, {barId: 1, barRatio: .5, status: "red", whenLow: true}, {barId: 1, barRatio: .25, status: "black-flag", whenLow: true}, {barId: 1, barRatio: 0, status: "dead", whenLow: true},]; on("change:token", function(obj) { CONFIG.forEach(function(opts) { var maxValue = parseInt(obj.get("bar" + opts.barId + "_max")); var curValue = parseInt(obj.get("bar" + opts.barId + "_value")); log(opts.barId + ": " + curValue + "/" + maxValue); if (!isNaN(maxValue) && !isNaN(curValue)) { var markerName = "status_" + opts.status; if (curValue <= (maxValue * opts.barRatio)) { obj.set(markerName, opts.whenLow); } else { obj.set(markerName, !opts.whenLow); } } }); });
1659316537
The Aaron
Roll20 Production Team
API Scripter
So, the problem is with the algorithm you've chosen.  You're only considering a threshold to pass, not ranges.  On each change of the token, it will consider the ratio compared to each barRatio you've specified.  Right now it will go like this for a few sample values: .85 ::= do nothing .70 ::= Turn on Yellow (.70 <= .75) .45 ::= Turn on Yellow (.45 <= .75), turn on Red (.45 <= .5) .15 ::= Turn on Yellow (.15 <= .75), turn on Red (.15 <= .5), turn on Black-Flag (.15 <= .25) 0.0 ::= Turn on Yellow (0.0 <= .75), turn on Red (0.0 <= .5), turn on Black-Flag (0.0 <= .25), turn on Dead (0.0 < = 0) What you want is to Turn Yellow on when it is between [0.75, .5), 0.75 >= n > .5 You could do that a few ways, but the simplest is to just turn everything off, then search for the one status that needs to be on. As an aside, you have this set up to store the bar as a part of each status config, but would that actually be different at any point?  Same for your whenLow property?  I'm going to rewrite this as I would do it for purely setting the correct status when in the correct range, then let you modify it further for other needs, if that's ok. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 on ( 'ready' , () => { const bar = 1 ; const CONFIG = [ { barRatio : . 75 , status : "yellow" }, { barRatio : . 5 , status : "red" }, { barRatio : . 25 , status : "black-flag" }, { barRatio : 0 , status : "dead" } ]; // testing for a number is annoying... =D const isNumber = ( n ) => /^\s*[-+]?\d+(?:\.\d*)?\s*$/ . test ( ` $ { n } ` ); // helper function to grab the ratio of a bar const barRatio = ( o , n ) => { let v = o . get ( ` bar$ { n } _value ` ); let m = o . get ( ` bar$ { n } _max ` ); if ( isNumber ( v ) && isNumber ( m )){ return v / m ; } return 1 ; // 1 will be generally ignored, so it's a safe default return }; on ( "change:token" , ( obj , prev ) => { // skip update if the bar didn't change if ( obj . get ( ` bar$ { bar } _value ` ) !== prev [ ` bar$ { bar } _value ` ] || obj . get ( ` bar$ { bar } _max ` ) !== prev [ ` bar$ { bar } _max ` ]) { // grab the bar ratio const ratio = barRatio ( obj , bar ); // build settings to turn off all statuses we're manipulating by default let ops = CONFIG . reduce (( m , c ) => ({... m ,[ ` status_$ { c . status } ` ] : false }),{}); // find the first barRatio (backwards) that the current ratio is less than let change = [... CONFIG ]. reverse (). find ( ( opts ) => ( ratio <= opts . barRatio ) ); // if we found one, turn it on if ( change ){ ops [ ` status_$ { change . status } ` ] = true ; } // this will clear all statuses, but set one if found in `change` above obj . set ( ops ); } }); }); I mostly annotated this in the code, but just a few highlights: Line 1: I've wrapped all the code in the on('ready',) event.  This both encapsulates your code in it's own namespace (out of the global namespace) and delays it from executing before the Mod Sandbox is fully loaded.  Generally, you will always want to do this, or at least delay registering events until ready has happened. Line 11: This is a function to check if a variable is a number.  There are lots of things it can be that aren't a number (Infinity, NaN, null, undefined, an array, an object, text, etc).  This function will pretty much account for that. Line 14: This is a function that does the work of figuring out the ratio.  It's nice to extract bits of code like this as functions.  It makes the code where it's used cleaner, and lets you reuse it elsewhere. Line 23: I've added the second argument to this function (prev), which lets line 25 check if there was a change to the bar and skip the logic if there wasn't. Line 31: I'm building an object containing updates to turn off all status markers the script has in CONFIG. Line 34: I'm searching backwards through CONFIG for the first threshold I'm under.  Since it's backwards, it will guarantee I only get the range from the prior marker's barRatio to current marker's barRatio.  .find() will return undefined if there are no matches. Line 37: If .find() found one, I'll change it to true on Line 38 Line 41: Setting all the status markers appropriately. This will set them all to false, with the exception of one found on line 34. Hope that helps! 
1659323677
GiGs
Pro
Sheet Author
API Scripter
Aaron in your test you used this: . test ( ` $ { n } ` ) Is there a reason not to do this? . test ( n ) ?
1659326396
The Aaron
Roll20 Production Team
API Scripter
Yeah, the regex matches a string, but n might be any number of things that aren't strings. Using `${n}` forces whatever n is to be converted to a string before the test happens. Anything convertible to a string that matches a number will be a valid number. If I'd done something like Number(n), that would artificially turn non-number things into numbers, like "" into 0. Since an empty bar should not be treated as if it was 0, that wouldn't work out for this. 
1659326883
GiGs
Pro
Sheet Author
API Scripter
Thanks, I havent done much with regex so that didnt ocur to me.
This makes so much more sense - thank you! I thought the original script I found was going to be a huge timesaver until I went down the rabbit hole of trying to modify and reconfigure. I appreciate the detailed explanation of the changes as well! Thank you!!!!