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

Need Help with Macro to handle Troll regen automagically (5e OGL, Pro)

I am the kind of GM who'll forget to auto regen the health of certain kinds of monsters, especially in large battles.  I've got a combat with 4 trolls.  The question is:  How to get the Troll to add 10 health at the beginning of its turn in combat? Since health is not tied to NPC char sheets, I can use ChatSetAttr API.  That leaves me with token mod.  The CombatTracker API nicely lets me automatically kick off a macro at the beginning of each character's turn (macro named: CT_TURN, by default).  I have two problems: How to specify (or auto-select) the token of the character whose turn it currently is, and How to stop augmenting health once the health is at full. Thanks in advance for any help (or, as the case may be, disappointing news).
1599666148
David M.
Pro
API Scripter
Maybe this little script by TheAaron would work for you? You create an ability called OnMyTurn (that invokes ChatSetAttr), and it should get run every time that character's turn comes up. Never used it, but seems like what you are asking for. <a href="https://app.roll20.net/forum/post/6608405/script-onmyturn-activate-an-ability-in-the-context-of-a-token-when-its-turn-occurs/?pageforid=6612955#post-6612955" rel="nofollow">https://app.roll20.net/forum/post/6608405/script-onmyturn-activate-an-ability-in-the-context-of-a-token-when-its-turn-occurs/?pageforid=6612955#post-6612955</a>
Thanks David M., It doesn't work, though.&nbsp; The problem with generic monsters is that the hp are not ""connected"" to the token (so ChatSetAttr doesn't work).&nbsp; Also, TokenMod, the API that seems ideal for changing hp in an instance like this requires the token to be "selected".&nbsp; Thus, OnMyTurn doesn't work.&nbsp;&nbsp; My best bet may be to use the CT_TURN to put a button in chat reminding me to do my job :) Thanks again!
1599710956
The Aaron
Roll20 Production Team
API Scripter
For OnMyTurn, you can use TokenMod and specify --ids @{selected|token_id}. OnMyTurn will substitute the token_id of the token whose turn it is and thereby heal the right one. Be sure to use the ! on the set part to restrict to the max.&nbsp;
1599717447

Edited 1599717692
Thanks!&nbsp; Sadly, I'm having problems....Doesn't seem like it can find the token_id attribute?&nbsp; Yet this first-pictured macro does find that attribute? &lt;EDIT: pictures didn't come through...put them in next posting&gt; What am I doing wrong, here?...Also, I don't know where to put the ! in --set ... (TokenMod docs are impressively extensive...so maybe I missed it somewhere?)
1599740942

Edited 1599741287
David M.
Pro
API Scripter
Hmm, yeah the "!" with the "+" doesn't seem to work for me, either. If not natively supported, you could try this way around it by adding a Target request in your OnMyTurn ability. Then add either a) 10 or b) the difference between the target bar1 max and current value. Keep lowest value in an inline roll. Something like&nbsp; !token-mod --set bar1_value|+[[{10, [[@{target|Troll Regen|bar1|max} - @{target|Troll Regen|bar1}]]}kl1 ]] --ids @{target|Troll Regen|token_id}
--ids @{target|Troll Regen|token_id} What is "Troll Regen" in this case?&nbsp; I guess I don't understand what the double pipe is doing here.
1599752802
GiGs
Pro
Sheet Author
API Scripter
You can supply an optional label to identify targets, which is handy when you want to target more than one token with the same macro. The "Troll Regen" part is such an optional label - it's not necessary here (at least, i don't think it is), but it does no harm to use it.
Is troll regen the name of character, then?
1599753397
GiGs
Pro
Sheet Author
API Scripter
No, its just a label that you assign that has no actual ,mechanical effect. Imagine you had a macro that allowed you to attack two different characters. You might try this: /roll 1d20&gt;@{target|AC} [attack 1] /roll 1d20&gt;@{target|AC} [attack 2] The above two lines are a single macro. You want to be able to attack two different targets - but this will prompt you only once, and use the same AC for both. You can fix this by supplying a unique label for each. The label can be anything. You could do /roll 1d20&gt;@{target|Foe 1|AC} [attack 1] /roll 1d20&gt;@{target|Foe 2|AC} [attack 2] Now each target call has a unique identifier, so you'll be prompted to sel;ect a diferent target for each. But since the labels can be anything at all, you could also do /roll 1d20&gt;@{target|Troll Regen|AC} [attack 1] /roll 1d20&gt;@{target|Jellybean Muffins|AC} [attack 2] The labels themselves have no mechanical effect. You pick labels that mean something to you, so you know what they mean when you read you macro again several months later.
1599765530

Edited 1599766745
David M.
Pro
API Scripter
Yes, as GiGs implied, "Troll Regen" was purely meant to be a reminder prompt for you, since the ability is going to get fired off automatically as soon as the troll's turn begins. This is to avoid the "I wonder why am I being asked for a target when I didn't do anything?" scenario. Can replace with "Click the Troll to regen hp" or something if you want. EDIT - You may also want to whisper a reminder to yourself about the target click (if you decide to go that route) as part of your OnMyTurn ability. Sometimes it's hard to notice the target prompt at the top of the screen, so a simultaneous chat message reminder might help.
1599766038
The Aaron
Roll20 Production Team
API Scripter
Just to verify OnMyTurn is working, try creating your ability like this: You should get output like: here's the latest copy of the code, just in case: on('ready', () =&gt; { const playerCanControl = (obj, playerid='any') =&gt; { const playerInControlledByList = (list, playerid) =&gt; list.includes('all') || list.includes(playerid) || ('any'===playerid &amp;&amp; list.length); let players = obj.get('controlledby') .split(/,/) .filter(s=&gt;s.length); if(playerInControlledByList(players,playerid)){ return true; } if('' !== obj.get('represents') ) { players = (getObj('character',obj.get('represents')) || {get: function(){return '';} } ) .get('controlledby').split(/,/) .filter(s=&gt;s.length); return playerInControlledByList(players,playerid); } return false; }; const resolver = (token,character) =&gt; (text) =&gt; { const attrRegExp = /@{(?:([^|}]*)|(?:(selected)|(target)(?:\|([^|}]*))?)\|([^|}]*))(?:\|(max|current))?}/gm; const attrResolver = (full, name, selected, target, label, name2, type) =&gt; { let simpleToken = JSON.parse(JSON.stringify(token)); let charName = character.get('name'); type = ['current','max'].includes(type) ? type : 'current'; const getAttr = (n, t) =&gt; ( findObjs({type: 'attribute', name:n, characterid: character.id})[0] || {get:()=&gt;getAttrByName(character.id,n,t)} ).get(t); const getFromChar = (n,t) =&gt; { if('name'===n){ return charName; } return getAttr(n,t); }; const getProp = (n, t) =&gt; { switch(n){ case 'token_name': return simpleToken.name; case 'token_id': return simpleToken._id; case 'character_name': return charName; case 'bar1': case 'bar2': case 'bar3': return simpleToken[`${n}_${'max'===t ? 'max' : 'value'}`]; } return getFromChar(n,t); }; if(name){ return getFromChar(name,type); } return getProp(name2,type); }; return text.replace(attrRegExp, attrResolver); }; const runCommandsForToken = (token) =&gt; { if(token &amp;&amp; token.get('represents')){ let character = getObj('character',token.get('represents')); let ability = findObjs({ name: 'OnMyTurn', type: 'ability', characterid: character.id }, {caseinsensitive: true})[0]; let TCRes = resolver(token,character); if(ability){ let content = TCRes(ability.get('action')).replace(/\[\[\s+/g,'[['); try { sendChat(character.get('name'),content); } catch(e){ log(`OnMyTurn: ERROR PARSING: ${content}`); log(`OnMyTurn: ERROR: ${e}`); } } // look for a macro findObjs({ type: 'macro', name: 'OnMyTurn' }, {caseinsensitive: true}) .forEach(macro=&gt;{ // if gm macro, always run. Otherwise, run if the token is controlled by the player if(playerIsGM(macro.get('playerid')) || playerCanControl(character, macro.get('playerid'))){ let content = TCRes(macro.get('action')).replace(/\[\[\s+/g,'[['); try { sendChat(character.get('name'),content); } catch(e){ log(`OnMyTurn: ERROR PARSING: ${content}`); log(`OnMyTurn: ERROR: ${e}`); } } }); } }; const checkOnMyTurn = (obj,prev) =&gt; { let to=JSON.parse(obj.get('turnorder')||'[]'); let toPrev=JSON.parse(prev.turnorder||'[]'); if(to.length &amp;&amp; to[0].id!=='-1' &amp;&amp; to[0].id !== (toPrev[0]||{}).id){ let token = getObj('graphic',to[0].id); runCommandsForToken(token); } }; on('chat:message', (msg)=&gt;{ if('api'===msg.type &amp;&amp; /^!omt\b/i.test(msg.content) &amp;&amp; playerIsGM(msg.playerid)){ checkOnMyTurn(Campaign(),{turnorder:JSON.stringify([{id:-1}])}); } }); on( 'change:campaign:turnorder', (obj,prev)=&gt;setTimeout(()=&gt;checkOnMyTurn(Campaign(),prev),1000) ); on('chat:message', (msg) =&gt; { if('api'===msg.type &amp;&amp; /^!eot\b/.test(msg.content)){ setTimeout(()=&gt;checkOnMyTurn(Campaign(),{turnorder:JSON.stringify([{id:-1}])}),1000); } }); }); I really should get this in the one click...
Thanks for the explanation of the labels. That API script IS different than what I had (54 lines longer, thank you!).&nbsp; However it still doesn't add health. Even if I hard code the token ID, it doesn't work ( i.e.,&nbsp;!token-mod --set bar1_value|+10 --ids -MGkiMSneSTdQq-wYw28 ) Seems like the problem may be between TokenMod and OnMyTurn?&nbsp; I can't get a single command from TokenMod to work.&nbsp; Other API scripts work (I tried "!aura config" and got an error message from the script that there were insufficient permissions -- indicating the script is getting called, at least).&nbsp; Before using the current code for OnMyTurn, I was getting an error in the API sandbox console, but there is no longer that feedback.&nbsp; &nbsp;Can others get TokenMod and OnMyTurn to play nicely together?&nbsp; If yes, is there some configuration issue to consider?
1599969302

Edited 1600049499
OMG, Nevermind!&nbsp; Token mod has a setting to allow players to use --ids.&nbsp; Once this was selected, it worked as expected!&nbsp; &nbsp;Thanks to all who helped! EDIT:&nbsp; Also worthy of note, the --ignore-selected option is a requirement for this particular application (any selected tokens will also be healed, otherwise).&nbsp; Also, in the end, I did make this a chat-macro-button because Trolls don't necessarily regenerate every round.
1600086454

Edited 1600086899
Trying to add another post, but something keeps going wrong....Will edit incrementally: I wanted to have this be a pop-up query rather than a chat-macro-button.&nbsp; The following code works from chat but causes an error from OnMyTurn: !token-mod {{&nbsp; --set bar1_value|+[[?{Fire or Acid damage to @{selected|character_name} this round|No,{10&amp;#44; [[@{selected|bar1|max} - @{selected|bar1}]]&amp;#125;kl1|Yes,0}]]&nbsp; --ignore-selected&nbsp; --ids @{selected|token_id}&nbsp; }} The same message appears in the API Output Console: May I impose for more help, please?