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] RealizeStatusmarkers -- Create attributes on you player characters that represent the number on statusmarkers.

So I'm setting up a global macro (since my group has a couple bard NPCs) for Inspiration + the formula for the die level. !me Target will use a [[?{What is your Bard Level Range?|1 to 4,3|5 to 9,4|10 to 14,5|15 up,6}*2]]-sided die for the inspire you just granted them. !token-mod{{    --set      statusmarkers|+all-for-one:?{What is your Bard Level Range?}    -- ids      @{target|token_id} }} What I want to be able to do is set up another macro that can pull the number from the token's status marker, then roll the appropriate die given.  Because the status markers can only go from 1-9, I have to set it up in half values and multiply it back together. What I need is to be able to acquire and utilize the status marker's number in a macro.  I'm not sure what would reference it, such as "@{target|statusmarker|all-for-one_value}" (which is a complete foo on my part, having no idea what it is) so that I could get a roll query together on that: [[ 1d[[ @{target|statusmarker|all-for-one_value}*2]] ]] in the example given Aaron, any ideas on this?
1532383172
The Aaron
Pro
API Scripter
I think this is getting beyond what you'd want to use TokenMod alone for.  I'd suggest using TokenMod to set a statusmarker as a reminder, and using ChatSetAttr to add/update an attribute with the die size.
Well, with token mod I can set the variables (turn "all-for-one" on, set number to half the die roll) But what can Make Use of those settings?  When I saw the fact that you added numbers to statusmarkers, I was inspired (hehehe - get it? *sigh*) on the different ways you could make use of the number, other than a visual representation for things like counting rounds down or increasing strength levels.  We should be able to DO something with them as variables, but to do that, we have to be able to draw the value into a macro.  That's the part I don't know how to do.
1532392467
The Aaron
Pro
API Scripter
Ah, good point... ok... For any character that has a controlled by (i.e. Player characters, for the most part (Yes Scott C., this could use the PartyManager at some point... like after we've written it!)) this will create attributes named "sm_<STATUS>", so "sm_blue", "sm_snail", "sm_all-for-one", etc.  Initially, they will all have a 0 value, but changing the statusmarker on a token that represents a character with a controlled by will cause the attribute to be updated to the same number. Changing with TokenMod will similarly change the markers. (In the case of multi-markers using the [] syntax, it will basically be one of the values, not easily determined which one, so don't rely on it.) If you have multiple tokens all representing a character, only the last change to a token will be reflected, e.g.: if you have two tokens with red:3  on them, and you take it off of one, @{selected|sm_red} will be 0 on both of them.  I suggest using the --ids @{selected|character_id} on TokenMod to keep all the tokens in sync. Script: on('ready',() => { const markers=[ 'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', 'dead', 'skull', 'sleepy', 'half-heart', 'half-haze', 'interdiction', 'snail', 'lightning-helix', 'spanner', 'chained-heart', 'chemical-bolt', 'death-zone', 'drink-me', 'edge-crack', 'ninja-mask', 'stopwatch', 'fishing-net', 'overdrive', 'strong', 'fist', 'padlock', 'three-leaves', 'fluffy-wing', 'pummeled', 'tread', 'arrowed', 'aura', 'back-pain', 'black-flag', 'bleeding-eye', 'bolt-shield', 'broken-heart', 'cobweb', 'broken-shield', 'flying-flag', 'radioactive', 'trophy', 'broken-skull', 'frozen-orb', 'rolling-bomb', 'white-tower', 'grab', 'screaming', 'grenade', 'sentry-gun', 'all-for-one', 'angel-outfit', 'archery-target' ]; const markerAttrRegexp=/^sm_(?:red|blue|green|brown|purple|pink|yellow|dead|skull|sleepy|half-heart|half-haze|interdiction|snail|lightning-helix|spanner|chained-heart|chemical-bolt|death-zone|drink-me|edge-crack|ninja-mask|stopwatch|fishing-net|overdrive|strong|fist|padlock|three-leaves|fluffy-wing|pummeled|tread|arrowed|aura|back-pain|black-flag|bleeding-eye|bolt-shield|broken-heart|cobweb|broken-shield|flying-flag|radioactive|trophy|broken-skull|frozen-orb|rolling-bomb|white-tower|grab|screaming|grenade|sentry-gun|all-for-one|angel-outfit|archery-target)$/; 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, s, v) => getOrCreateAttr({ type: 'attribute', name: `sm_${s}`, 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(/,/) .map((s)=>s.split(/@/)) .forEach( s => setStatus(character.id,s[0])) ; sm.split(/,/) .map((s)=>s.split(/@/)) .forEach( s => setStatus(character.id,s[0],s[1])) ; } } } },10); on('change:graphic',handleStatusMarkerChange); /* 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, ...markers.map( s => ({characterid: c.id, name: `sm_${s}`, current: 0}) )], []); 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(); });
1532406501
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
This could be part of party manager! ;) Couldn't resist Seriously though looking forward to chatting at GenCon :)
1532422618

Edited 1532422807
rats - i can't get it to draw a value in the macro: &{template:5e-shaped}{{title=Bard inspiration Roll}}{{text=You use your inspiration to twist fate! Rolled:  [[ 1d[[ @{selected|sm_one-for-all} *2]] ]] }} !token-mod{{    --set      statusmarkers|-all-for-one:0 }}
1532440240
The Aaron
Pro
API Scripter
Try: &{template:5e-shaped}{{title=Bard inspiration Roll}}{{text=You use your inspiration to twist fate! Rolled:  [[ 1d[[ @{selected|sm_all-for-one} *2]] ]] }} !token-mod{{    --set      statusmarkers|-all-for-one:0 }}
1532440430
The Aaron
Pro
API Scripter
First line is doing @{selected|sm_all-for-one} by itself, then the roll template using it.
lol - i see what you did there, or rather - what i did there.  Evidently I wanted to complete the 3 musketeers phrase... which explains a lot of the reason it didn't work b4 ...
1532468847
The Aaron
Pro
API Scripter
=D Hope you got it working now!  I'm already thinking about how I can use this...
As opposed to me, pure genius.  We now have a working macro method to Set and Roll Bardic Inspiration.  I'll repost macros for those that want to use, and the dependancies are the Self-Identify API and TokenMod API, brilliantly both coded by TheAaron. To set, target the PC/ Creature to give inspiration to: !me Target will use a [[?{What is your Bard Level Range?|1 to 4,3|5 to 9,4|10 to 14,5|15 up,6}*2]]-sided die for the inspire you just granted them. /w @{target|token_name} You now have Bardic Inspiration, granted by me!  The number on the status marker is equal to half the die rolled (3 = a d 6, etc...).  Once within the next 10 minutes, you can roll the die and add the number rolled to one ability check, attack roll, or saving throw you make. You can wait until after you roll the D20 before deciding to use the Bardic Insp⁠iration die, but must decide before the DM says whether the roll succeeds or fails. Once the Bardic Inspiratio⁠n die is rolled, it is lost. You can have only one Bardic Inspir⁠ation die at a time.  !token-mod{{    --set      statusmarkers|+all-for-one:?{What is your Bard Level Range?}    -- ids      @{target|1|token_id} }} If I was good at nesting and math, I'd probably just have the Initialize Macro discover the casting bard's level and assign the half die value from a range, but the macro works fine as a roll query. To use, select your token &{template:5e-shaped}{{title=Bard inspiration Roll}}{{text=You use your Bardic Inspiration to twist fate! Rolled:  [[ 1d[[@{selected|sm_all-for-one}*2]] ]] }} !token-mod{{    --set      statusmarkers|-all-for-one:0 }}
Who CAN'T use this?!!!  lol ... well, I guess a world that Loathes Bards .... but who'd want to play in such a world? ... gahhh ....
1532470617
The Aaron
Pro
API Scripter
Cool. =D
The Aaron said: =D Hope you got it working now!  I'm already thinking about how I can use this... Oh - I'm thinking that Turnmarker or Heartbeat could count rounds down to initiate detonations or drop spell buffs .... yeah - there are so many practical applications. There is a snag though.  Your current counter only allows 1-9 as "on" values, and 0 is "off".  Without being able to go into double and triple counts, you really can't do as much as I could dream up ... but hey, why hold that grenade any longer than 10 seconds unless you really didn't like writing letters ...
1532471206
The Aaron
Pro
API Scripter
=D  I could make a version that supported multiple digits with repeating status markers, but lets just go with what we have for now... =D
sure sure.  The deal is great as it is ... You know, by now, how much I love to automate tasks and have the players get that "Wow" effect.  I'm still learning a majority of information and doing the best I can to grasp it all, and often get confused.  That's why I praise you as much as I do, because you've helped me immensely.  In a matter of a few weeks, my game has gone from average to "wow".  Sometimes simple things like phrasing being backwards, etc. can block me, but sometimes we work together in getting good ideas that I hope the rest of the community is benefitting from. And you have me very interested in what you and Scott C are working on with this Party Manager ... If you need someone to beta test it, as long as I 'get it', let me know.  I can sign an NDA if ya need ...
1532472164

Edited 1532472220
The Aaron
Pro
API Scripter
HAHAHAH.  Well, during GenCon week, Scott and I will have to chat  entmoot on that. =D
hint hint Scott ... though I still have no conceivable idea what I'm hinting for ... lol
Hey The Aaron, Could you make a version of this script that returns 0 for the Status Marker not set at all and 1 for the status marker set with no value? inactive = 0 active w/o any numerical value = 1 active w/ any numerical value = the associated numerical value
0 is the default "unset" value.  1 would be a value.  But what you're looking for isn't there yet, as is my request for up to 3 digit values (1-999).  I think what you're looking for that won't interfere with other's use of the script is an "N" value which would need a whole script rewrite, as this would pull in the parser as "NaN", or 'not a number' that the API would have to preparse to mean something else on output or it would confuse the Roll20 Engine.  I think it would be simpler to say what you want the "NaN" output to function as.
I think what you might want, if I'm not mistaken, is for status markers to output a more textual reference, based on your previous examples.  I'm thinking your idea has merit, but might be more achievable from chat menu prompting than the use of status markers; but I'm not sure what game system your utilizing, etc...
1534682198
The Aaron
Pro
API Scripter
Actually, I know what he wants and it should be easy to do. I’ll try and get a version with that later today. 
1534702984
The Aaron
Pro
API Scripter
In fact, it's only a line or two change and I think this is so intuitive that it should be the default behavior.  Here is a new copy of the script with the default behavior of "on with no number has a value of 1": on('ready',() => { const markerOnValue = 1; const markers=[ 'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', 'dead', 'skull', 'sleepy', 'half-heart', 'half-haze', 'interdiction', 'snail', 'lightning-helix', 'spanner', 'chained-heart', 'chemical-bolt', 'death-zone', 'drink-me', 'edge-crack', 'ninja-mask', 'stopwatch', 'fishing-net', 'overdrive', 'strong', 'fist', 'padlock', 'three-leaves', 'fluffy-wing', 'pummeled', 'tread', 'arrowed', 'aura', 'back-pain', 'black-flag', 'bleeding-eye', 'bolt-shield', 'broken-heart', 'cobweb', 'broken-shield', 'flying-flag', 'radioactive', 'trophy', 'broken-skull', 'frozen-orb', 'rolling-bomb', 'white-tower', 'grab', 'screaming', 'grenade', 'sentry-gun', 'all-for-one', 'angel-outfit', 'archery-target' ]; const markerAttrRegexp=/^sm_(?:red|blue|green|brown|purple|pink|yellow|dead|skull|sleepy|half-heart|half-haze|interdiction|snail|lightning-helix|spanner|chained-heart|chemical-bolt|death-zone|drink-me|edge-crack|ninja-mask|stopwatch|fishing-net|overdrive|strong|fist|padlock|three-leaves|fluffy-wing|pummeled|tread|arrowed|aura|back-pain|black-flag|bleeding-eye|bolt-shield|broken-heart|cobweb|broken-shield|flying-flag|radioactive|trophy|broken-skull|frozen-orb|rolling-bomb|white-tower|grab|screaming|grenade|sentry-gun|all-for-one|angel-outfit|archery-target)$/; 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, s, v) => getOrCreateAttr({ type: 'attribute', name: `sm_${s}`, 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(/,/) .map((s)=>s.split(/@/)) .forEach( s => setStatus(character.id,s[0])) ; sm.split(/,/) .map((s)=>s.split(/@/)) .forEach( s => setStatus(character.id,s[0],parseInt(s[1],10)||markerOnValue)) ; } } } },10); on('change:graphic',handleStatusMarkerChange); /* 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, ...markers.map( s => ({characterid: c.id, name: `sm_${s}`, current: 0}) )], []); 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(); }); To get back to the old behavior, you just need to change the 1 on line 2 to a 0: const markerOnValue = 0; Cheers!
Thank you so much!  I'll try this out, I'm sure it'll work perfectly!