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

Attempting an API to change the bar's color by HP%

1642630196
MAMS Gaming
Pro
Sheet Author
This is a branch from another topic: <a href="https://app.roll20.net/forum/post/10513115/script-healthstate/?pageforid=10612350#post-10612350" rel="nofollow">https://app.roll20.net/forum/post/10513115/script-healthstate/?pageforid=10612350#post-10612350</a> Basically, I am trying to simulate a changing bar color by using all 3 bars, and only having one of them display at a time. As mentioned in the original thread, I'm using Finds' HealthState API as a base. I got it working as long as I don't try to link it to a character sheet. A trigger for each of the 3 bars (with a check, so we don't get an infinite loop): handle1 = function(obj) { &nbsp;&nbsp;&nbsp;&nbsp;if ('graphic' == obj.get('type') &amp;&amp; 'token' == obj.get('subtype')) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var new_value = obj.get('bar1_value') || 0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (obj.get('bar2_value') != new_value) {obj.set("bar2_value",new_value);} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (obj.get('bar3_value') != new_value) {obj.set("bar3_value",new_value);} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;handleToken(obj,new_value); &nbsp;&nbsp;&nbsp;&nbsp;} }, A check for which one currently has the max: if (obj.get('bar1_max')!="X") { &nbsp;&nbsp;&nbsp;&nbsp;var token_hp_max = (obj.get('bar1_max')) || 0; } else if (obj.get('bar2_max')!="X") { &nbsp;&nbsp;&nbsp;&nbsp;var token_hp_max = (obj.get('bar2_max')) || 0; } else if (obj.get('bar3_max')!="X") { &nbsp;&nbsp;&nbsp;&nbsp;var token_hp_max = (obj.get('bar3_max')) || 0; } then a version of Finds' check for which health level they are currently at: if (token_hp &gt; token_yellow_max) { &nbsp;&nbsp;&nbsp;&nbsp;// Green Health &nbsp;&nbsp;&nbsp;&nbsp;obj.set({bar1_max:token_hp_max, bar2_max:"X", bar3_max:"X"}) } else if (token_hp &gt; token_red_max &amp;&amp; token_hp &lt;= token_yellow_max) { &nbsp;&nbsp;&nbsp;&nbsp;// Yellow Health &nbsp;&nbsp;&nbsp;&nbsp;obj.set({bar1_max:"X", bar2_max:token_hp_max, bar3_max:"X"}) } else if (token_hp &lt;= token_red_max) { &nbsp;&nbsp;&nbsp;&nbsp;// Red Health &nbsp;&nbsp;&nbsp;&nbsp;obj.set({bar1_max:"X", bar2_max:"X", bar3_max:token_hp_max}) } That part all works great. The problems I encounter all come when I try to attach this to the value on a character sheet. My first attempt was to modify the bar's link: var token_hp_link = (obj.get('bar1_link')) || 0; ... obj.set("bar2_link",token_hp_link); This only half associates them, as changing either will change what's on the sheet, but the value listed in the bar never changes again. I read somewhere that there is both a value on the token that links to the sheet, and a value on the sheet that links to the token. I don't yet know how to change that sheet value from the API. My next attempt was to ignore the "link" attribute, and use the API to transfer values between them. I can read "health" and "health_max" from the character sheet, but I still can't figure out how to write to them. On top of that, I would need to set up an&nbsp;&nbsp;on("change:...") event to trigger when the health attribute changes, so this seems to be a futile attempt. Whichever way I approach this, I seem to need to know how to write to the character sheet associated with the token. Can anyone help?
1642631016

Edited 1642631130
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Question: Is there a reason you are continually re-linking the bars? Wouldn't it be simpler to link all three to HP and just change visibility of each token bar based on current HP value? Never mind. 'Twas a silly quesiton.
1642631383

Edited 1642631489
MAMS Gaming
Pro
Sheet Author
That would be wonderful. The only attribute I've found for that only hides the text overlay, not the bar itself. Is there an attribute that hides the bar too? I'll take your edit as a no. Too bad, cause that really would have solved it.
1642631906

Edited 1642631919
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
No, I had a brain hiccup. I forgot that bars with a current and max value always display for the GM/controller.
1642644612

Edited 1642645656
Oosh
Sheet Author
API Scripter
Maybe have a look at tokenMod, as it seems to change the bar link without issues. From memory, there is an internal function for refreshing token links buried somewhere but I don't think it's exposed to the API. edit - hmmmm... it seems like just changing barX_link should be enough. How are you trying to change the value? Changing bar1 won't change bar2, even if both are linked to the same attribute. You need to change the attribute itself to trigger all linked bars, e.g. !setattr --sel --hp|2 If you have all 3 bars linked to hp, you should see all 3 of them change. To jump from the token to the sheet, you want the 'represents' value . Note that it's an id which you'll need to look up, not a reference to the sheet. In terms of the character sheet linking back to the token - AFAIK that's only the defaulttoken blob. You can only grab it asynchronously, and it's read-only. Changes propagate from the character sheet to linked tokens via the Events system, not by direct coupling.
1642646968
timmaugh
Forum Champion
API Scripter
If you use TokenMod for this, as Oosh suggested, you could augment that with a metascript mule to give you the same 3 ranges of green, yellow, and red. Feed the quotient of the current hits / max hits to the mule, and return arguments for the token mod statement.
1642647503
Oosh
Sheet Author
API Scripter
Take one cup of Aaron's code, stir in 3 generous spoonfuls of Tim's Meta. The result should be... well, it's a Solution , innit?
1642648026
timmaugh
Forum Champion
API Scripter
Solution is... one word for it. =D
1642654232
Oosh
Sheet Author
API Scripter
1642654777
MAMS Gaming
Pro
Sheet Author
Oosh said: &nbsp;it seems like just changing barX_link should be enough. How are you trying to change the value?&nbsp; I started with using obj.get &amp; obj.set for everything. For the link I used: var token_hp_link = (obj.get('bar1_link')) || 0; ... obj.set({bar1_link:0, bar3_link:0, bar1_max:"X", bar3_max:"X", bar2_link:token_hp_link}) which is what created that weird half-associated error. Then, just now I tried: var character = obj.get('represents') || ''; ... sendChat('','!token-mod --set bar1_link|health --ignore-selected --current-page --ids '+character); or var token_name = obj.get('name') || ''; ... sendChat('','!token-mod --set bar1_link|health --ignore-selected --current-page --ids @{'+token_name+'|character_id}'); and no link appears at all. timmaugh said: If you use TokenMod for this, as Oosh suggested, you could augment that with a metascript mule to give you the same 3 ranges of green, yellow, and red. Feed the quotient of the current hits / max hits to the mule, and return arguments for the token mod statement. I understood very little of that. I should point out that I'm very new to this. I only taught myself js to be able to play with Roll20. You did bring up how I am defining my ranges. I'm using the same code as the character sheet: // Define Yellow Max &amp; Red Max (*1 ensures token_hp_max is treated as a number) var token_yellow_max = (Math.round((token_hp_max*1+2)*0.7)); var token_red_max = (Math.round((token_hp_max*1+2)/3)); if (token_hp_max === 37) { &nbsp;&nbsp;&nbsp;&nbsp;var token_yellow_max = 28; } else if (token_hp_max === 19) { &nbsp;&nbsp;&nbsp;&nbsp;var token_yellow_max = 14; }
1642658552

Edited 1642658675
Oosh
Sheet Author
API Scripter
A couple of things to check, before you get carried away trying to fix code that might be fine: Does this work with a token selected: !token-mod --set bar1_link|health If not, is 'health' 100% the correct name for the attribute? Does it have a set MAX value? Is the token definitely linked to a character sheet? If it does work, there's probably something the matter with tokenId references somewhere. If token-mod isn't working, there's something amiss with the Campaign/token/character setup somewhere. Possibly duplicate attribute names? You could try with another attribute that has some unusual letters in it to isolate the issue.
1642696512

Edited 1642698975
timmaugh
Forum Champion
API Scripter
MAMS Gaming said: timmaugh said: If you use TokenMod for this, as Oosh suggested, you could augment that with a metascript mule to give you the same 3 ranges of green, yellow, and red. Feed the quotient of the current hits / max hits to the mule, and return arguments for the token mod statement. I understood very little of that. I should point out that I'm very new to this. I only taught myself js to be able to play with Roll20. You did bring up how I am defining my ranges. I'm using the same code as the character sheet: // Define Yellow Max &amp; Red Max (*1 ensures token_hp_max is treated as a number) var token_yellow_max = (Math.round((token_hp_max*1+2)*0.7)); var token_red_max = (Math.round((token_hp_max*1+2)/3)); if (token_hp_max === 37) { &nbsp;&nbsp;&nbsp;&nbsp;var token_yellow_max = 28; } else if (token_hp_max === 19) { &nbsp;&nbsp;&nbsp;&nbsp;var token_yellow_max = 14; } No worries, MAMS. Here's a primer on the metascript toolbox . The basic idea is that they get into the command line sent to other scripts (or standard chat messages) to give you more flexibility to have that command line adapt to the current state of the game. To demonstrate your need, I'll start small and build out from there. The Goal First, as I understand it, your goal is to have only one of the bars of the token to be visible at a time. Dividing the range of 100% of current HP into 3 ranges, then, means that you would have bar 1 (the green bar) visible for an hp percentage over 66%, and bar 3 (the red bar) visible for any hp percentage under 34%. (Those numbers are easily changed; you'll see in a minute.) As Oosh said, TokenMod can do this for you, but what you're lacking is the ability of that one TokenMod command line to adapt to the current situation of the token/character. You don't want 3 different TokenMod commands with you having to select which one to run; you want a single command line that detects the state of things and makes the correct adjustment to the token's bars. The Base Command The TokenMod command that we'll modify would look like this for making bar 1 visible and represent the attribute "hp": !token-mod --set bar1_link|hp bar2_link| bar3_link| bar2_max|x bar3_max|x That links the first bar, unlinks the other two, and sets the "max" values of the bars we want to hide to be "x". There would be another option for making bar 2 visible, and a third option for bar 3. Using a Mule as an Off-Loaded Case Test One thing about the Muler script is that it's structure allows for it to use the variables housed in a Mule as a way to off-load case tests from a macro. It could make a macro very hard to read if you had to account for 20 different possible cases in the text of the macro, itself -- if you even could, with no if/then logic available from Roll20 syntax or case testing aside from some creative inline roll manipulation. A Mule has a list of variables that will return input to the command line, and they can be constructed as numeric ranges to allow for finding a result easily. In your case, we need three entries: less than 34, between 34 and 66, and greater than 66 (again, you can move those numbers to adjust your ranges). So we set our Mule up like this: &lt;34= ... 34-66= ... &gt;66= ... If we feed it a range under 34, we'll get the value we put to the right of the = sign. What is the thing that we will feed? The percentage of current HP out of Max HP, expressed as a range of 0 to 100: [[floor((@{selected|hp}/@{selected|hp|max})*100)]] One thing about inline rolls and metascripts is that metascripts only mess with the command line where you tell them to. In other words, they don't unpack a given inline roll unless you want them to. If you do nothing, the roll will pass through to be dealt with by the receiving script (in this case, TokenMod, which handles its own inline-roll-unpacking). We need the value before the message gets to TokenMod, so we have to extract the value of the inline roll so that we can feed it to the Mule. We do that by adding .value to the end of the inline roll (a ZeroFrame syntax structure). [[floor((@{selected|hp}/@{selected|hp|max})*100)]].value Now we know if the character has 10 out of 20 HP, the value of the roll will be 50, and 50 should fall into that middle line of the Mule. Loading the Mule and Retrieving the Value A mule is loaded using the {&amp;mule} statement, and variables are retrieved with a get.../get. So let's say the Mule (ability) is named "HealthIndicators" and the mule-character housing that ability is called "MuleBoy". The {&amp;mule} statement would look like this: {&amp; mule MuleBoy.HealthIndicators} ...and our get statement, using the roll we just established, would look like this: get.MuleBoy.HealthIndicators.[[floor((@{selected|hp}/@{selected|hp|max})*100)]].value/get The {&amp;mule} syntax can go anywhere in the macro and be detected. The get statement will return the value of the variable from the Mule, so we definitely want it in the right place. What it needs to return is the different TokenMod command line variations depending on those three levels. So, taking another look at the Mule, the full structure have the appropriate TokenMod syntax as the returned value for each level. &lt;34=bar3_link|hp bar2_link| bar1_link| bar2_max|x bar1_max|x 34-66=bar2_link|hp bar1_link| bar3_link| bar1_max|x bar3_max|x &gt;66=bar1_link|hp bar2_link| bar3_link| bar2_max|x bar3_max|x Sub-Finalized Command Line What we're retrieving are the component parts of the TokenMod --set argument, so we make sure the Muler get statement is in that position in our Token-Mod command line, and we arrive at our command line of: !token-mod --set get.MuleBoy.HealthIndicators.[[floor((@{selected|hp}/@{selected|hp|max})*100)]].value/get {&amp;mule MuleBoy.HealthIndicators} Running that for a Token will apply the correct linkages and values to make the appropriate bar visible. The above command will work for a single token selected. REQUIRED SCRIPTS: &nbsp;TokenMod, ZeroFrame, Muler Hydrate This is your friendly, neighborhood MetaMancer letting you know it's time to hydrate. You've been at this a while, and we're about to initiate you into the deeper meta-mancery. ...There. Feeling better? Let's continue. Taking It Further TokenMod works on the selected tokens OR on token ids fed to the&nbsp; --ids &nbsp;argument. So you could feed multiple token IDs to the command line, except that the way the above roll math is constructed, everyone will have the same bar visible, no matter the state of the HP % (the selected-hp and selected-hp-max are pulled one time by Roll20 before the API gets involved, the math is performed, and the result is then used by TokenMod for all tokens it should affect). We want differentiated results for each token. We can achieve that by adding SelectManager and Fetch to our metascript toolbox. forselected SelectManager gives us a forselected handle that iterates an API command over the set of selected tokens. Just by pre-pending a script command line with forselected, you will send that command through one time for every selected token, and for each resulting message there will be only one token selected (first token1, then token2, etc.). !forselected token-mod --set ... That doesn't change the fact that the math is done already. For that we need Fetch. Fetch Fetch lets us get data from the selected token (or a token by id or name, a character sheet, or the speaker)&nbsp; after&nbsp; Roll20 parsing is done, but&nbsp; before&nbsp; standard scripts get involved. Fetch constructions look a lot like standard Roll20 constructions, just with parentheses instead of braces. Also, you can use periods rather than pipes -- which I often do just to clue the eye in to the fact that this is a Fetch call, not a standard R20 construction: @(selected.hp) @(selected.hp.max) Timing So far we've got our initial message sending out a message for each token selected. We want to hold off on the parser "discovering" our Fetch construction until those secondary messages go out. Remember, each of those messages has a different selected token, so our Fetch constructions (reading from the selected token) will return differentiated data. If we let the Fetch constructions be detected in the first message, we'll be back in the same boat we were at the beginning, with a single value being retrieved and the math being performed prior to sending out our various TokenMod messages (every token will display its HP in the bar designated by the math performed on the values of the FIRST selected token). We call delaying the discovery of a metascript construction "deferring", and we can defer constructions in a forselected statement by designating a deferral character. I often use a caret (^), unless the downstream command line actually would need/use that character... then I might use a † (alt-0134 on a Windows machine), or a ‡ (alt-0135). You declare it like this: !forselected(^) ... Now we can use that to break up the downstream constructions we don't want to have detected until we are in the messages forselected dispatches. @^(selected.hp) @^(selected.hp.max) {^&amp; mule ...} get^.HealthIndicators... Timing Part Deux If you're paying attention, those Fetch constructions are inside an inline roll: [[floor((@(selected.hp)/@(selected.hp.max))*100)]] Inline rolls are parsed by Roll20 before metascripts can get in there to provide those values, so if we leave things like this, the inline roll will break with bad syntax. That means now that we have subbed in Fetch constructions for the standard Roll20 attribute calls, we have to defer the inline rolls. For a normal Fetch construction in a normal roll in a normal message, that would mean utilizing [\][\]...\]\] syntax for the inline roll: [\][\]floor((@(selected.hp)/@(selected.hp.max))*100)\]\] In your case, with the Fetch construction already being deferred until the secondary message dispatched by forselected , we have to ALSO defer our (already-deferred) inline rolls UNTIL that message: [\^][\^]floor((@^(selected.hp)/@^(selected.hp.max))*100)\^]\^] Sub-Finalized Command Line With the appropriate deferrals giving us token-specific data at the time that the scripts need it, our command line now looks like this: !forselected(^) token-mod --set get^.MuleBoy.HealthIndicators.[\^][\^]floor((@^(selected.hp)/@^(selected.hp.max))*100)\^]\^].value/get {^&amp;mule MuleBoy.HealthIndicators} That will work for multiple tokens selected, giving you differentiated results for each. REQUIRED SCRIPTS: TokenMod, ZeroFrame, Muler, Fetch, SelectManager Letter Home from MetaScript Camp Hi, mom... It's my first day at metascript camp and I already hate it. My instructor is so mean. He made us do compounded deferrals in our very first session. We were metamancing so hard, the kid next to me vomited. I want to come home. --Your Kid&nbsp;(if loving parents actually send their own kids to a hell-hole like this) Taking It Further-er So far, we've built our command line around having the token(s) actually selected on the VTT. This can be even more hands-off if you use the SelectManager method of virtually selecting the tokens: {&amp; select token_id 1 , token_id 2 , Token Name 1 , Token Name 2 , ... } That construction overwrites the set of selected tokens from the tabletop (without actually changing the selection status of anything on the board) with the list you specify. As far as the message is concerned, the list you provide is the set of tokens selected (provided they all map to a token the script can locate). So you could provide the list of tokens you want to manipulate, making sure that list represents the tokens in combat at the time: {&amp;select Cuddles, Glarg, Bubbles Magoo, Biddy the Kill} Then your forselected statement would iterate over that list of tokens, dispatching the relevant TokenMod command with the appropriate data for each. You could keep that list on a character sheet (say, on the same MuleBoy character already referenced) and retrieve it with an attribute call: {&amp;select @{MuleBoy|TokensInCombat} } (I'm currently working on a Query metascript that would let you select your tokens even more dynamically than this, but it's not ready/released yet.) Relatively-Finalized Command Line The {&amp;select} syntax can be placed anywhere in the command line. We don't defer it, because we want it detected prior to the messages being dispatched to TokenMod: !forselected(^) token-mod --set get^.MuleBoy.HealthIndicators.[\^][\^]floor((@^(selected.hp)/@^(selected.hp.max))*100)\^]\^].value/get {^&amp;mule MuleBoy.HealthIndicators}&nbsp;{&amp;select @{MuleBoy|TokensInCombat} } That will iterate the TokenMod command over the tokens listed (comma-separated) in the attribute retrieved from the MuleBoy character. You will NOT have to select them on the VTT. REQUIRED SCRIPTS: TokenMod, ZeroFrame, Muler, Fetch, SelectManager Final Considerations That's a pretty powerful statement, but it does come with the caveats that (1) you have to run it manually, and (2) it is only for tokens with the given attribute you are trying to map to the bars... that is, if your characters have HP but your NPCs have "npc_hp", the line will need more tweaking to include NPCs and characters in the same macro processing. That could be accomplished by incorporating APILogic (another metascript) syntax in the value retrieved from the Mule, but I've already had half the kids leave MetaScript Camp at this point, so I'll just point out that this is available, at this point. Short of using that method, you could have a macro for characters and a separate one for NPCs (if you wanted/needed to affect both). As for running it manually, this is where you could either code a listener to fire off this macro, or you could use Customizable Roll Listener to listen for attack template verbiage, and fire off this script automagically. That would require more testing.
1642744736
MAMS Gaming
Pro
Sheet Author
Oosh said: A couple of things to check, before you get carried away trying to fix code that might be fine: Does this work with a token selected: !token-mod --set bar1_link|health If not, is 'health' 100% the correct name for the attribute? Does it have a set MAX value? Is the token definitely linked to a character sheet? If it does work, there's probably something the matter with tokenId references somewhere. If token-mod isn't working, there's something amiss with the Campaign/token/character setup somewhere. Possibly duplicate attribute names? You could try with another attribute that has some unusual letters in it to isolate the issue. If I use&nbsp; !token-mod --set bar1_link|health or !token-mod --set bar1_link|health --ignore-selected --current-page --ids @{Absolute Zero|character_id} in chat, they work perfectly. if I use sendChat('','!token-mod --set bar1_link|health --ignore-selected --current-page --ids -MrStYNTDlRGGah9SPHV'); or sendChat('','!token-mod --set bar1_link|health --ignore-selected --current-page --ids @{Absolute Zero|character_id}'); from an API, I get the same half associated bug I get when I used obj.set("bar2_link",token_hp_link); Considering that there are no references to the selected token (in fact, it's got --ignore-selected), I don't see how it could be something faulty elsewhere in the code. My only thought is that I am changing variables that have triggers set, so the code might be running multiple times simultaneously. Just in case the variable "health" from the character sheet was the problem I created an arbitrary variable called "manapoints" (there are no other variables in SCRPG that use _max, which is why I can get away with using all 3 bars). When I set the code to change which bar is linked to manapoints, it has the same half associated bug. I think tomorrow, I will take some time to create a script that only uses a simple trigger, and then that line of code. If that works, I'll add things back in until it breaks. timmaugh said: but it does come with the caveats that (1) you have to run it manually It's fascinating stuff, and I got past the water break when I realized, you were leading to (1). My code currently has on('change:graphic:bar1_value', handle1); on('change:graphic:bar2_value', handle2); on('change:graphic:bar3_value', handle3); I need the code to trigger each time health changes. I have everything working except assigning barX_link. (from inside my API that is. I can assign it fine using !token-mod in chat)
1642817358

Edited 1642817623
Oosh
Sheet Author
API Scripter
A couple of things - first, you've got --ids @{Absolute Zero|character_id} Is this a typo? You need to pass tokenMod a token id, obviously, not a character id. Secondly, you might need to try the --api-as option. Without cracking TM open and reading Aaron's code, my guess is that passing in --ids only works for the GM, and a sendChat message is not from the GM. So you'll need your playerId to paste in there. You can grab it from a chat message/the Campaign data in your script, or manually from the browser console at: Campaign.players._byId The first entry should be the game's Creator (presumably you). Then pass it to token-mod: !token-mod --api-as -abcdefg124345636 &lt;rest of command line&gt; Alternatively, just Meta your way around/through/over the problem as Tim suggested :)
1643159073
MAMS Gaming
Pro
Sheet Author
Sorry for delayed response. I got called out of town for a few days, and got distracted by a breakthrough in another script. On the plus side, I have my first fully functioning API now. Oosh said: A couple of things - first, you've got --ids @{Absolute Zero|character_id} Is this a typo? You need to pass tokenMod a token id, obviously, not a character id. The tokenMod API is more thorough than that. If you put a character id in, it affects all tokens associated with that character. Oosh said: Secondly, you might need to try the --api-as option. Without cracking TM open and reading Aaron's code, my guess is that passing in --ids only works for the GM, and a sendChat message is not from the GM. So you'll need your playerId to paste in there. You can grab it from a chat message/the Campaign data in your script, or manually from the browser console at: Campaign.players._byId The first entry should be the game's Creator (presumably you). Then pass it to token-mod: !token-mod --api-as -abcdefg124345636 &lt;rest of command line&gt; One feature of tokenMod is a toggle for if players can use --ids. !token-mod --config players-can-ids|on I can get everything else to change using the ids, but am still getting that bug whenever I try to use a script to change the link. Oosh said: Alternatively, just Meta your way around/through/over the problem as Tim suggested :) There is an obstacle to using the Meta method, and can't see a way to get around it. The whole point of this is to trigger whenever any PC changes their health, and if I create an API with a trigger then I am right back to calling the code from within a script.