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

[Script][Snippet] HPBarManager - Keep HP between 0 and max, account for Temporary HP automatically

1536446160

Edited 1536446270
The Aaron
Pro
API Scripter
This snippet manages some aspects of token HP Bars: Keep HP bar from going negative Keep HP bar from going over max Mark tokens that move to 0 HP with a statusmarker (default: dead , the red X) If a token represents a character and has the HP bar linked to that character: Mark tokens with a status marker (default: chained-heart ) if their character has a value for the "temp_hp" attribute greater than 0.  If the value is between 1 and 9, the number of "temp_hp" will be on the marker. Automatically adjust reductions to HP so that they come from the "temp_hp" attribute first. The practical upshot of all this is it makes temporary HP easy to use and hard to forget about.  It also observes TokenMod and ChatSetAttr so that if you make a change with them, it will do the right thing. This works out of the box with the Shaped 5e DnD sheet, using bar 3 as the health, but can be easily adjusted using the 4 variables at the top. Code: /* global TokenMod, ChatSetAttr */ on('ready', () => { // Configuration parameters const HPBarNum = 3; const TempHPMarker = 'chained-heart'; const DeadMarker = 'dead'; const TempHPAttributeName = 'temp_hp'; ///////////////////////////////////////////// const bar = `bar${HPBarNum}_value`; const lnk = `bar${HPBarNum}_link`; const max = `bar${HPBarNum}_max`; const mrk = `status_${TempHPMarker}`; const ded = `status_${DeadMarker}`; const checkTempHP = (obj) => { let v = parseFloat(obj.get('current')); findObjs({ type: 'graphic', represents: obj.get('characterid') }) .filter( (t) => t.get(lnk) !== '') .forEach((g)=>{ g.set(mrk,(v>0 ? (v>9 ? true : v) : false) ); }); }; const assureTempHPMarkers = () => { let queue = findObjs({ type: 'attribute', name: TempHPAttributeName }); const burndownQueue = ()=>{ if(queue.length){ let attr = queue.shift(); checkTempHP(attr); setTimeout(burndownQueue,0); } }; burndownQueue(); }; const temporalTempHPCache = {}; const accountForHPBarChange = (obj,prev) => { // 1. did hp change and is it a scale const hpMax = parseInt(obj.get(max),10); let hp = parseInt(obj.get(bar),10); const diff = hp-parseFloat(prev[bar]); if( !isNaN(hpMax) && diff !== 0 ) { let changes = {}; // 2. does it represent a character // 3. does the hp bar represent an attribute const character = getObj('character',obj.get('represents')); if( diff < 0 && character && obj.get(lnk)!=='' ){ // 4. is there temp hp const temp_hp = findObjs({ type: 'attribute', characterid: character.id, name: TempHPAttributeName })[0]; if( temp_hp ) { const now = Date.now(); // 5. have we accounted for it. if( !temporalTempHPCache.hasOwnProperty(character.id) || (now-temporalTempHPCache[character.id].when)>300 ) { // calculate necessary change const tempHP = parseFloat(temp_hp.get('current'))||0; const newTmpHP = Math.max((tempHP+diff),0); const toHeal = tempHP - newTmpHP; temporalTempHPCache[character.id]={ when: now, toHeal: toHeal }; temp_hp.set('current', newTmpHP); checkTempHP(temp_hp); } hp += temporalTempHPCache[character.id].toHeal; changes[bar] = hp; } } if(hp > hpMax) { hp = hpMax; changes[bar] = hp; changes[ded] = false; } else if(hp <= 0) { hp=0; changes[bar] = hp; changes[ded] = true; } else { changes[ded] = false; } obj.set(changes); } }; const onAttributeChange = (obj) => { if(obj.get('name') === TempHPAttributeName){ checkTempHP(obj); } }; on("change:attribute", onAttributeChange); on("change:token", accountForHPBarChange); if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(accountForHPBarChange); } if('undefined' !== typeof ChatSetAttr && ChatSetAttr.registerObserver){ ChatSetAttr.registerObserver('change',onAttributeChange); } assureTempHPMarkers(); }); I combined this with my OnMyTurn snippet to implement my Order of the Immortal Mystic's temp HP every round. =D Enjoy! Support my work on If you use my scripts, want to contribute, and have the spare bucks to do so , go right ahead. However, please don't feel like you must contribute just to use them! I'd much rather have happy Roll20 users armed with my scripts than people not using them out of some sense of shame. Use them and be happy, completely guilt-free! Disclaimer: This Patreon campaign is not affiliated with Roll20; as such, contributions are voluntary and Roll20 cannot provide support or refunds for contributions.
1536446454
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Very, very nice! Loaded, tested and appreciated! You should write more scripts. ;)
Thanks Aaron. This will be very useful.
1536510346
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
If you use ChatSetAttr, here is a little macro I wrote for this. It allows you to set the temp hp without opening your sheet, and also reports the current value (in case it is higher than 9). Change "temp_hp" to whatever attribute you edited into the script settings. Use on any selected token. !setattr --sel --silent --temp_hp|?{@{selected|token_name} currently has @{selected|temp_hp} temporary HP. Enter new value|@{selected|temp_hp}}
Would it be possible to have the script exclude a certain character? I make heavy use of Matt's Door Script and the way I created the switches the first value of the red bar are often higher then the max.
1536610977
The Aaron
Pro
API Scripter
The answer must be yes, but the question will be what criteria to apply to make that happen.  Is there a particular feature of the token that could be used to differentiate it?  Possibly the name or the character it represents?
Sure thing! The token name is always: Switch.
1536612321

Edited 1536613786
The Aaron
Pro
API Scripter
Ok, easy peasy.  Add this line (bold) and it will ignore all tokens named switch (case ignored): const accountForHPBarChange = (obj,prev) => { if(/switch/i.test(obj.get('name'))){ return; } // 1. did hp change and is it a scale
Yey for script-magic! Thanks a lot!!!
1536613332
The Aaron
Pro
API Scripter
no problem! =D
1536613741

Edited 1536613756
Ravenknight
KS Backer
Ack! I got an error:  For reference, the error message generated was:  SyntaxError: Unexpected token {
1536613812
The Aaron
Pro
API Scripter
You should copy it again... I'm sure I totally didn't leave out a ) and just fix it in the message above...
Ah, it worked! My bad. :D
1536614015
The Aaron
Pro
API Scripter
HAHAHAHAHA! =D