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

Show players a less-accurate / fuzzy health bars

So I want to show my players an indication of how injured a monster is. I know about Aura/Tint HealthColors but a bit concerned with performance issues, some of my players are struggling with lag as it is. Also I'd rather not use tint and aura visually. The simplest option would be to show players the monster health bars without numbers, which is fine, but I would like to be able to show a less accurate. more fuzzy version of that. For example, just show a health bar that inc/decrements in 20% steps. So if the health bar is actually 93%, it would show as 80%, if it's actually 13% it would show as 0%, or 1%. So the players would have a pretty good idea, but not the exact %. One way I can think of is to have a secondary bar that's tied to the primary health bar, and have that bar updates automatically whenever the primary bar is changed. How hard would this be to do? Is there existing scripts that I can modify to achieve this? Or, are there other ways to achieve this?
1674049990
The Aaron
Roll20 Production Team
API Scripter
Not very difficult. I have a little script I whipped up for someone that puts a description in the tooltip, it could be modified to show an approximation in another bar, or even use the name. 
1674055202
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
A script could also place a token marker color dot with 8, 6, 4, 2 numbers on them. Bonus, they could also be color coded.
1674058462

Edited 1674058527
The Aaron
Roll20 Production Team
API Scripter
Here's a script that should do what you want.  It's set up to look for health in bar 3 and update bar 1 with the fuzzy health.  You can edit lines 3-6 to configure it: const HEALTH_BAR = 3; //< Where to find the health of the token const DISPLAY_BAR = 1; //< Where to put the fuzzy health on the token const SEGMENTS = 5; //< How many "levels" of fuzzy health const SEGMENT_GONE_AT_HALF = false; //< Where to consider a segment "gone" SEGMENTS controls how many subdivisions there are for the fuzzy health bar.  If you set this to 100, you'll get a percentage for your fuzzy health bar. SEGMENT_GONE_AT_HALF controls when a segment is removed.  Default is when it is all gone, for example: 50/50 hp menas 5/5 segments 44/50 hp means 5/5 segments when false, or 4/5 when true 1/50 hp means 1/5 segments when false, or 0/5 when true On startup it will apply the bars to all tokens in the game that are NPCs, where NPC is defined as a token that can't be controlled by a player (either on the token or the character it represents).  NOTE: this will change bar 1 by default on all NPC tokens, so be sure you have configured it for your game before saving it in the API! It supports TokenMod, so if you are changing token health with that, it will update correctly. Here's the code: on('ready',()=>{ const HEALTH_BAR = 3; //< Where to find the health of the token const DISPLAY_BAR = 1; //< Where to put the fuzzy health on the token const SEGMENTS = 5; //< How many "levels" of fuzzy health const SEGMENT_GONE_AT_HALF = false; //< Where to consider a segment "gone" const bound = (v, max, min) => Math.min(max,Math.max(min,v)); const isNPC = (o) => { if(undefined !== o && 'function' === typeof o.get){ switch(o.get('type')){ case 'graphic': if(o.get('represents')){ let c = getObj('character',o.get('represents')); return isNPC(c); } return (0 === o.get('controlledby').length); case 'character': return (0 === o.get('controlledby').length); default: return; // undefined is indepterminate } } return; // undefined is indepterminate }; const dress_func = (SEGMENT_GONE_AT_HALF ? Math.round : Math.ceil); const UpdateTooltipOnToken = (t) => { let ratio = (parseFloat(t.get(`bar${HEALTH_BAR}_value`))||0)/(parseFloat(t.get(`bar${HEALTH_BAR}_max`))||1); t.set({ [`bar${DISPLAY_BAR}_value`]: dress_func(bound( (SEGMENTS*ratio), SEGMENTS, 0)), [`bar${DISPLAY_BAR}_max`]: SEGMENTS, [`showplayers_bar${DISPLAY_BAR}`]: true, [`playersedit_bar${DISPLAY_BAR}`]: true }); }; const changeHandler = (t,p)=>{ if(t.get('represents')!==''){ if( isNPC(t) && ((t.get(`bar${HEALTH_BAR}_value`)!== p[`bar${HEALTH_BAR}_value`]) || (t.get(`bar${HEALTH_BAR}_max`)!== p[`bar${HEALTH_BAR}_max`])) ){ UpdateTooltipOnToken(t); } } }; const addHandler = (t,p)=>{ if(t.get('represents')!==''){ setTimeout(()=>{ if(isNPC(t)){ UpdateTooltipOnToken(t); } },300); } }; on('add:graphic',addHandler); on('change:graphic',changeHandler); if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(changeHandler); } const applyToTokens = ()=>{ let tokens = findObjs({type:'graphic'}).filter(t=>t.get('represents')!==''); const burndown = ()=>{ let t = tokens.shift(); if(t){ if(isNPC(t)){ UpdateTooltipOnToken(t); } setTimeout(burndown,0); } else { log('All tokens have updated fuzzy health bars.'); } }; burndown(); } applyToTokens(); });
Awesome, thanks ! I shall be "borrowing" that and using it in my games too.
The Aaron said: Here's a script that should do what you want. Oh wow, thanks a lot, I loaded this as a new custom script and it just works! I'll play around with it and report back. Thanks again!
1674137773
GiGs
Pro
Sheet Author
API Scripter
Erudo said: I loaded this as a new custom script and it just works! That happens a lot with Aaron's scripts.
1674145390
The Aaron
Roll20 Production Team
API Scripter
Awesome!  Glad to hear it's working for you, definitely let me know if you have any issues or other needs. =D
1674169953

Edited 1674171285
All testing in my sandbox campaign works great so far. I noted that this applies the 'see' permission for the fuzzy health bar as well, which is convenient. All new tokens are set up with the fuzzy health bars. And all existing tokens, including those in other pages? Or are those just set up when the page loads? I don't know much about the event handling in Roll20 but I'm wondering if this script would introduce lag to lesser computers?
1674170774

Edited 1674171327
I modded the script to use floor as the segmenting method. I then added a 'last sliver' mode so that when it gets to almost zero, it shows a sliver of hp (using half segment) instead of showing zero hp, to let the players know that they're almost there, but not quite yet. Examples for a 4 segment (quarters) health bar: 50% to 75% hp 25% to 50% hp less than 25% but not zero yet (last sliver) zero hp Modified script here, mind the dodgy coding, I'm super rusty: on('ready',()=>{ const HEALTH_BAR = 1; //< Where to find the health of the token const DISPLAY_BAR = 3; //< Where to put the fuzzy health on the token const SEGMENTS = 4; //< How many "levels" of fuzzy health const SEGMENT_GONE_AT_HALF = false; //< Where to consider a segment "gone" const LAST_SLIVER_MODE = true; //< Show last sliver of hp as 1/2 segment const bound = (v, max, min) => Math.min(max,Math.max(min,v)); const isNPC = (o) => { if(undefined !== o && 'function' === typeof o.get){ switch(o.get('type')){ case 'graphic': if(o.get('represents')){ let c = getObj('character',o.get('represents')); return isNPC(c); } return (0 === o.get('controlledby').length); case 'character': return (0 === o.get('controlledby').length); default: return; // undefined is indepterminate } } return; // undefined is indepterminate }; const dress_func = (SEGMENT_GONE_AT_HALF ? Math.round : Math.floor); const UpdateTooltipOnToken = (t) => { let ratio = (parseFloat(t.get(`bar${HEALTH_BAR}_value`))||0)/(parseFloat(t.get(`bar${HEALTH_BAR}_max`))||1); let actualHP = parseFloat(t.get(`bar${HEALTH_BAR}_value`))||0; let segmentedHP = dress_func(bound( (SEGMENTS*ratio), SEGMENTS, 0)); let segmentedMax = SEGMENTS; // for last sliver mode we need to double the segments if (LAST_SLIVER_MODE){ segmentedMax = SEGMENTS * 2; // check if showing last sliver if ((actualHP > 0) && (segmentedHP == 0)){ segmentedHP = 1; } else { segmentedHP = segmentedHP * 2; } } t.set({ [`bar${DISPLAY_BAR}_value`]: segmentedHP, [`bar${DISPLAY_BAR}_max`]: segmentedMax, [`showplayers_bar${DISPLAY_BAR}`]: true, [`playersedit_bar${DISPLAY_BAR}`]: true }); }; const changeHandler = (t,p)=>{ if(t.get('represents')!==''){ if( isNPC(t) && ((t.get(`bar${HEALTH_BAR}_value`)!== p[`bar${HEALTH_BAR}_value`]) || (t.get(`bar${HEALTH_BAR}_max`)!== p[`bar${HEALTH_BAR}_max`])) ){ UpdateTooltipOnToken(t); } } }; const addHandler = (t,p)=>{ if(t.get('represents')!==''){ setTimeout(()=>{ if(isNPC(t)){ UpdateTooltipOnToken(t); } },300); } }; on('add:graphic',addHandler); on('change:graphic',changeHandler); if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(changeHandler); } const applyToTokens = ()=>{ let tokens = findObjs({type:'graphic'}).filter(t=>t.get('represents')!==''); const burndown = ()=>{ let t = tokens.shift(); if(t){ if(isNPC(t)){ UpdateTooltipOnToken(t); } setTimeout(burndown,0); } else { log('All tokens have updated fuzzy health bars.'); } }; burndown(); } applyToTokens(); });
1674173855
The Aaron
Roll20 Production Team
API Scripter
Cool. =D The script will fix the fuzzy bar for all tokens in the game at startup.  That's what the applyToTokens() function at the bottom does.  This happens on the API Server, so no lag to any clients.  It uses what I call the burndown method to make the updates so that it doesn't lag out other API scripts.  It will take a few seconds to finish, but you can look at the API log for the message "All tokens have updated fuzzy health bars." to know when it's finished.  It should also make appropriate changes to newly created tokens.