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 .
×

Is there a way to integrate API math checks into character sheet macros (or read the value of status markers without API)?

Hello. I am trying to make it so that a status marker can be read by a macro (i.e. apply +X based on X of selected token, where X is the number on a status marker). To my understanding this is only accomplishable through API. However, is there a way to integrate API smoothly within an existing character sheet field? For example, in a D&D5E NPC sheet, is there a way to add API math within the attack bonus of an attack action? Or otherwise, is there an API that can update character attributes based on what you selected, "live" more or less? I think a workaround I could accomplish is, theoretically, making a character sheet button that does an API command first to update the selected token's relevant attribute based on status markers, then on a new line running the roll macro command itself. That does sound cumbersome potentially though. Thanks.
1790119529
The Aaron
Roll20 Production Team
API Scripter
You are correct, you need an API script involved.  I have one I wrote that depends on libTokenMarkers from the 1-click.  This script will create attributes on the character named sm_MARKER_NAME, so sm_blue, sm_poisoned, etc. for every available status marker and set the value to 0.  It will then adjust the value to whatever number you assign to it.  I wrote this script a while ago, so let me know if it has any problems, and be sure to install libTokenMarkers along with it. Code: /* global libTokenMarkers */ on('ready',() => { // Make sure libTokenMarkers exists, and has the functions that are expected if('undefined' === typeof libTokenMarkers || (['getStatus','getStatuses','getOrderedList'].find(k=> !libTokenMarkers.hasOwnProperty(k) || 'function' !== typeof libTokenMarkers[k] )) ) { // notify of the missing library sendChat('',`/w gm <div style="color:red;font-weight:bold;border:2px solid red;background-color:black;border-radius:1em;padding:1em;">Missing dependency: libTokenMarkers</div>`); } else { const markerOnValue = 1; let allMarkers = libTokenMarkers.getOrderedList(); let tagToMarkerLookup = allMarkers.reduce((m,tm)=>{ m[tm.getTag()]=tm; return m; },{}); const markerAttrRegexp=new RegExp(`^sm_(?:${allMarkers.map(m=>m.getName()).join('|')})$/;`,'i'); const getOrCreateAttr = (()=>{ let cache = findObjs({ type: 'attribute'}) .filter( a => markerAttrRegexp.test(a.get('name'))) .reduce( (m,a) => { const cid=a.get('characterid'); m[cid]=m[cid]||{}; m[cid][a.get('name')]=a; return m; },{}); return (p)=>{ if(!cache.hasOwnProperty(p.characterid) || !cache[p.characterid].hasOwnProperty(p.name)){ cache[p.characterid]=cache[p.characterid]||{}; cache[p.characterid][p.name] = createObj('attribute',p); } return cache[p.characterid][p.name]; }; })(); const setStatus = (cid, m, v) => getOrCreateAttr({ type: 'attribute', name: `sm_${m.getName()}`, characterid: cid }).set('current',parseInt(v,10)||0); const handleStatusMarkerChange = _.debounce((obj,prev) => { if(obj.get('represents').length) { let character = getObj('character',obj.get('represents')); if(character && character.get('controlledby').length){ let sm = obj.get('statusmarkers'); if(sm!==prev.statusmarkers){ prev.statusmarkers.split(/,/) .filter(s=>s.length) .map((s)=>s.split(/@/)) .forEach( s => setStatus(character.id,tagToMarkerLookup[s[0]])) ; sm.split(/,/) .filter(s=>s.length>0) .map((s)=>s.split(/@/)) .forEach( s => setStatus(character.id,tagToMarkerLookup[s[0]],parseInt(s[1],10)||markerOnValue)) ; } } } },10); const markersFor = (c) => allMarkers.map( m => ({characterid: c.id, name: `sm_${m.getName()}`, current: 0}) ); const preCreateMarkersOn = (c) => { let queue = markersFor(c); const dequeue = () => { let prop = queue.shift(); if(prop){ getOrCreateAttr(prop); setTimeout(dequeue,0); } }; dequeue(); }; const handleControlledbyChange = (obj,prev) => { if( 0 === prev.controlledby.length) { preCreateMarkersOn(obj); } }; on('change:graphic',handleStatusMarkerChange); on('change:character:controlledby',handleControlledbyChange); /* global TokenMod */ if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(handleStatusMarkerChange); } const workQueue = findObjs({ type: 'character' }) .filter( c => c.get('controlledby').length ) .reduce( (m,c) => [...m, ...markersFor(c)], []); log(`RealizeStatusmarkers: Initializing attributes: ${workQueue.length}`); const drainQueue = () => { let prop = workQueue.shift(); if(prop){ getOrCreateAttr(prop); setTimeout(drainQueue,0); } else { log(`RealizeStatusmarkers: Finished initializing attributes.`); } }; drainQueue(); } });
1790133968

Edited 1790134162
The Aaron said: You are correct, you need an API script involved.  I have one I wrote that depends on libTokenMarkers from the 1-click.  This script will create attributes on the character named sm_MARKER_NAME, so sm_blue, sm_poisoned, etc. for every available status marker and set the value to 0.  It will then adjust the value to whatever number you assign to it.  I wrote this script a while ago, so let me know if it has any problems, and be sure to install libTokenMarkers along with it. Code: [...] Hello, thank you! I think I was looking around and found your old RealizeStatusmarkers which is sort of what I was looking for. Is what you posted going to work with a character sheet that is represented by multiple tokens that have different status markers?
1790214657
The Aaron
Roll20 Production Team
API Scripter
No, since attributes are tied to a character, not a token, it can only ever have one sm_blue or similar.