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

ApplyDamage Macro/API To remove temp HP 1st (bar2) before removing bar1

Is there an api that would do this?  Something like !ApplyDamage @{selected|token_id} 20   Applied to a token with 20 in bar1 (normal HP), 5 in bar3 (temp_hp) and have it take 5 points from bar 3 and the rest from bar1.  I am fine with a macro using token-mod also if that is possible, but I am having a hard time figuring the syntax to make the math work. :) This is what I use now !token-mod --set bar1_value|-[[{@{selected|bar1},floor(?{Damage}*?{Damage Adj|None,1|Resistant,.5|Immune,0|Vulnerable,2|Super Vulnerable,3})}kl1]] And it works great.  I want to add a part that subtracts the value in bar3 from the total damage (and they another call to token-mod to remove HP from bar 3 also).
1625255685
The Aaron
Roll20 Production Team
API Scripter
Here is my HPBarManager snippet, which should support ApplyDamage changes.  Just change the HPBarNum to 1, and set TempHPAttributeName to whatever attribute holds your temp hp: /* global TokenMod, ChatSetAttr */ on('ready', () => { // Configuration parameters const HPBarNum = 3; const TempHPMarker = 'chained-heart'; const DeadMarker = 'dead'; const BloodiedMarker = 'red'; const TempHPAttributeName = 'hp_temp'; const UseBloodied=true; ///////////////////////////////////////////// const clearURL = /images\/4277467\/iQYjFOsYC5JsuOPUCI9RGA\/.*.png/; const bar = `bar${HPBarNum}_value`; const lnk = `bar${HPBarNum}_link`; const max = `bar${HPBarNum}_max`; const unpackSM = (stats) => stats.split(/,/).reduce((m,v) => { let p = v.split(/@/); let n = parseInt(p[1] || '0', 10); if(p[0].length) { m[p[0]] = Math.max(n, m[p[0]] || 0); } return m; },{}); const packSM = (o) => Object.keys(o) .map(k => ('dead' === k || true === o[k] || o[k]<1 || o[k]>9) ? k : `${k}@${parseInt(o[k])}` ).join(','); const checkTempHP = (obj) => { let v = parseFloat(obj.get('current')); findObjs({ type: 'graphic', represents: obj.get('characterid') }) .filter( (t) => t.get(lnk) !== '') .filter( (t) => !clearURL.test(t.get('imgsrc') ) ) .forEach((g)=>{ let sm = unpackSM(g.get('statusmarkers')); if(v>0){ sm[TempHPMarker]=v; } else { delete sm[TempHPMarker]; } g.set({ statusmarkers: packSM(sm) }); }); }; 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(); const tempHP = parseFloat(temp_hp.get('current'))||0; const newTmpHP = Math.max((tempHP+diff),0); const toHeal = tempHP - newTmpHP; const hash=`${character.id}:${hp}:${hpMax}:${toHeal}`; // 5. have we accounted for it. if( !temporalTempHPCache.hasOwnProperty(hash) || (now-temporalTempHPCache[hash].when)>10000) { // calculate necessary change temporalTempHPCache[hash]={ when: now, toHeal: toHeal }; temp_hp.set('current', newTmpHP); checkTempHP(temp_hp); } hp += temporalTempHPCache[hash].toHeal; changes[bar] = hp; } } let sm = unpackSM(obj.get('statusmarkers')); if(hp > hpMax) { hp = hpMax; changes[bar] = hp; delete sm[DeadMarker]; if(UseBloodied) { delete sm[BloodiedMarker]; } } else if(hp <= 0) { hp=0; changes[bar] = hp; sm[DeadMarker] = true; if(UseBloodied) { delete sm[BloodiedMarker]; } } else if(UseBloodied && (hp <= (hpMax/2) ) ) { delete sm[DeadMarker]; sm[BloodiedMarker] = true; } else { delete sm[DeadMarker]; if(UseBloodied) { delete sm[BloodiedMarker]; } } changes.statusmarkers = packSM(sm); 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); } if('undefined' !== typeof ApplyDamage && ApplyDamage.registerObserver){ ApplyDamage.registerObserver('change',onAttributeChange); } assureTempHPMarkers(); });
I'll give that a try.  Thank Aaron!