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

Using the tooltip to show a character's status on hover?

Hello everyone! I was looking through the roll20 documentation about tooltips and saw that through APIs it si possible to display the current status of a token (half-health, ecc.) and couldn't help but wonder if there was a way to do it using TokenMod.
I'm not the most API savvy person, so apologies if it's an obvius question, and thanks to anyone that will help!

The example on the roll20 documentation:
https://wiki.roll20.net/images/2/2c/Tooltip-example-dec2021.gif

I don't use tooltips because this happens very often when tokenmod interacts with my tokens ingame that have had tooltips enabled and nobody has ever managed to explain or tell me how to stop this from occurring(no extensions doesn't change this):  

https://www.youtube.com/watch?v=mZdsYgQX9oo

April 13 (3 years ago)

Edited April 13 (3 years ago)
The Aaron
Roll20 Production Team
API Scripter

DM Eddie, I literally have no idea what that video is supposed to be showing. I'd suggest starting a bug thread or filing a bug report, or at least starting your own thread about it in API Forum. 

April 13 (3 years ago)
The Aaron
Roll20 Production Team
API Scripter

Ferry, are you looking for a script that will automatically adjust the tooltip to display a health note when the token is changed?  What would you like it to display and at what thresholds?

April 14 (3 years ago)

Edited April 14 (3 years ago)
David M.
Pro
API Scripter

Not exactly what you are describing, but I put this script together for fun a few months ago when tooltips first came out that effectively links one or more attribute values (it builds a string) to the tooltip, via the token's gmnotes field. Note this will only work on the Production server, not that that should be an issue. It wouldn't give qualitative health values though, unless you came up with another way to update some custom attribute with that string value. But, I'm sure Aaron could write exactly what you are looking for while simultaneously playing 3D chess and transcribing Homer's epics in Klingon so...


The Aaron said:

Ferry, are you looking for a script that will automatically adjust the tooltip to display a health note when the token is changed?  What would you like it to display and at what thresholds?


Yes, exactly!
What I was hoping to achieve is something like:

Health = 100% -> Tooltip = "Full Health"

Health < 100% && Health > 50% -> Tooltip = "Wounded"

Health < 51% && Health < 0 -> Tooltip = "Bloodied"

Health = 0 -> Tooltip = "Unconcious"

This way my players can have a general idea of the characters' health by hovering on them (eg. the cleric knows if it's time for a mass heal, or which dice to use for Toll the Dead).

Thank you so much for all the advice!



David M. said:

Not exactly what you are describing, but I put this script together for fun a few months ago when tooltips first came out that effectively links one or more attribute values (it builds a string) to the tooltip, via the token's gmnotes field. Note this will only work on the Production server, not that that should be an issue. It wouldn't give qualitative health values though, unless you came up with another way to update some custom attribute with that string value. But, I'm sure Aaron could write exactly what you are looking for while simultaneously playing 3D chess and transcribing Homer's epics in Klingon so...


Thank you so much!
At the moment we are using the compact bar as my players prefer its somewhat cleaner look, so your script is of great help even if it doesn't show a qualitative value, I'll make sure to include it in my game right away!

April 14 (3 years ago)
David M.
Pro
API Scripter

You could also just make the compact bars visible to all players. They would get a general sense of how the other players are doing health-wise without seeing the actual numbers. This would also free up the tooltip for other purposes. Full disclosure: I actually don't use tooltips in my games, as I've had multiple issues with tooltips staying visible and floating when the mouse is moved off the token. 


David M. said:

You could also just make the compact bars visible to all players. They would get a general sense of how the other players are doing health-wise without seeing the actual numbers. This would also free up the tooltip for other purposes. Full disclosure: I actually don't use tooltips in my games, as I've had multiple issues with tooltips staying visible and floating when the mouse is moved off the token. 

Atm they have the bars visible for their companions and not visible for enemies.
The major issue is that we used to play with the Health Aura API, so they had the color as a quick tell of how things were going, but I had to deactivate it because it clashed with the effects for other auras (Aura of Vitality, Aura of Protection ecc) and replaced them automatically.
So now we're trying to find a something that might replicate that effect to an extent.

I haven't seen the bug happening as of yet in my games, but I'll keep an eye out for it in case, thanks for the heads up! ^^


April 14 (3 years ago)
David M.
Pro
API Scripter

Ah ok. If you want, I have a version of Aura/Health found here that only uses Aura 2 so you can use Aura 1 for whatever you want. It also includes a registration for my SmartAoE script to recognize hp changes from that script but it's ignored if SmartAoE isn't installed.

April 14 (3 years ago)
The Aaron
Roll20 Production Team
API Scripter

Ok, here's a first pass at a script to do this:

If you don't want the bar, just change SHOW_BAR=true to SHOW_BAR=false on line 4.

Code:

on('ready',()=>{

  const BAR = 3;
  const SHOW_BAR=true;
  const SHOW_SUMMARY=true;

  const bound = (v, max, min) => Math.min(max,Math.max(min,v));
  const times = (n,f) => Array(n).fill(n).map(f);

  const BARLEN = 10;
  const shade = "▓";
  const lcap = "╞";
  const line = "═";
  const rcap = "╡";

  const ShadePart = (n) => times(n,()=>shade).join('');
  const LinePart = (n) => times(n,()=>line).join('');

  const GetBarLine = (v) => {
    let barLen = Math.floor(BARLEN*bound(v,1.0,0));
    let hpBar = `${ShadePart(barLen)}${LinePart(BARLEN-barLen)}`;
    if(v<1.0){
      hpBar = `${hpBar.slice(0,-1)}${rcap}`;
    }
    if(v<=0.0){
      hpBar = `${lcap}${hpBar.slice(1)}`;
    }
    return hpBar;
  };

  const GetSummaryLine = (v) => 
    ( v>=1.0 )  ? "Full Health" : 
    ( v>0.5  )  ? "Wounded"     : 
    ( v>0.0  )  ? "Bloodied"    : 
                  "Unconcious"  ;

  const UpdateTooltipOnToken = (t) => {
    let ratio = (parseFloat(t.get(`bar${BAR}_value`))||0)/(parseFloat(t.get(`bar${BAR}_max`))||1);
    let lines = [];
    if(SHOW_BAR){
      lines.push(GetBarLine(ratio));
    }
    if(SHOW_SUMMARY){
      lines.push(GetSummaryLine(ratio));
    }
    if(lines.length){
      t.set({
        show_tooltip: true,
        tooltip: lines.join(' ')
      });
    }
  };
  
  on('change:graphic',(t,p)=>{
    if(t.get('represents')!==''){
      if(
        (t.get(`bar${BAR}_value`)!== p[`bar${BAR}_value`]) ||
        (t.get(`bar${BAR}_max`)!== p[`bar${BAR}_max`])
      ){
        UpdateTooltipOnToken(t);
      }
    }
  });

  on('add:graphic',(t,p)=>{
    if(t.get('represents')!==''){
      UpdateTooltipOnToken(t);
    }
  });

  const applyToTokens = ()=>{
    let tokens = findObjs({type:'graphic'}).filter(t=>t.get('represents')!=='');
    const burndown = ()=>{
      let t = tokens.shift();
      if(t){
        UpdateTooltipOnToken(t);
        setTimeout(burndown,0);
      } else {
        log('All tokens have updated tooltips.');
      }
    };
    burndown();
  }

  applyToTokens();
});
April 14 (3 years ago)

Edited April 14 (3 years ago)
keithcurtis
Forum Champion
Marketplace Creator
API Scripter

Clever way to typographically indicate a bar!

April 14 (3 years ago)
The Aaron
Roll20 Production Team
API Scripter

Thanks!  I would have been done 2 hours ago if I wasn't fixated on such cleverness... =D

April 14 (3 years ago)
timmaugh
Forum Champion
API Scripter

You're getting some good advice & suggestions, Ferry... I promised I would provide a metascript example, so maybe this can fold in with another suggestion (metascript constructions can work in the command lines of other scripts... keep reading, you'll see how). To do this, I'll use Muler  and ZeroFrame.

Step 1: Construct Mule

Muler uses abilities (called "Mules") on source characters to provide a static table that can be accessed for specific row. In your situation, you're asking for 4 entries. I'm going to create a Mule called "TipStatus" on a character called "MuleBoy", and make the entries like this:

>=100=Full Health
51-99=Wounded
>0=Bloodied
<=0=Unconscious

...which might read a little easier if I clean up the spacing (note, don't enter it as I'm about to show; enter it as above, with no extra spaces at the start or around the = sign)...

>=100   = Full Health
51-99 = Wounded
>0 = Bloodied
<=0 = Unconscious

Save that Mule (ability), and make sure that anyone who will need to access that table of information has explicit rights to the character (there's a bug currently with granting rights to "All Players").

Step 2: Usage

We're going to use the percentage of the current HP against the table (Mule) you just created. To get the percentage of HP, you're basically going to divide the current HP over the max and convert it to a percentage. There are different ways to go about it, using token bars or character attributes. I'm going to use token bars since you might want to do this for mooks:

[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]]

That will compute correctly if you have numeric values in both current and max for bar1.

As that roll sits in the command line, it will be represented by a roll marker... something like $[[0]]. We need to replace that with the value of the roll. We do that with a ZeroFrame construction, appending .value to the end of the last closing bracket:

[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]].value

In the command line, all of that will parse down to the actual percentage of HP... something like 78. So we use that against the Mule to retrieve the appropriate value:

get.MuleBoy.TipStatus.[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]].value/get

In the same way that the roll parses out to the HP percentage, all of the above will parse out to the returned text from the Mule. The above example can be put into any script's command line (with a few exceptions) to have the value "pre-retrieved" before the main script receives the message... except that we also need one more thing in the line. We need to instruct Muler to load the appropriate Mule to make the rows (variables) available:

{&mule MuleBoy.TipStatus}

That needs to go somewhere in the command line where you will want to use the get statement.

Finished Example Using Token-Mod

Using the above 2 constructions in a token-mod command line (to have it set the token-tip) might look like this:

!token-mod --on show_tooltip --set tooltip|"get.MuleBoy.TipStatus.[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]].value/get"{&mule MuleBoy.TipStatus}


The caveat to this (and a few other suggestions you've received) is that this isn't automatically triggered just because the token received damage. You have to run it manually. But the benefit to this method is that you have a single macro that can be used for any token. Also, if you want to change the text or the breakpoints you don't have to change code... you just have to alter your Mule (table). Want a "Lightly Wounded" entry for 75% or better? Add a new line and your macro will automatically take it into account.

(Oh, and if you wanted to extend this to do multiple tokens at once, you could use SelectManager's forselected handle and replace the normal Roll20 constructions like @{selected|bar1} with deferred Fetch constructions... but now we're just getting jiggy in the metasauce kitchen.)

There is alot of stuff in this community where things can be literally accomplished just as easily without the need for a script, and I was gonna point that out here, but then I remembered that I'm guilty of this with the majority of what I have installed in my session.  So I will just chime in and ask, did they fix the bug where tooltips would pop up in the blackness of unexplored areas when the players move their mouses over them?  Cause if they haven't, The Aaron may want to set the max health version to not report anything in a tooltip.  

April 14 (3 years ago)
The Aaron
Roll20 Production Team
API Scripter

Ah, I'll have to check that. Good info, DM Eddie. 


David M. said:

Ah ok. If you want, I have a version of Aura/Health found here that only uses Aura 2 so you can use Aura 1 for whatever you want. It also includes a registration for my SmartAoE script to recognize hp changes from that script but it's ignored if SmartAoE isn't installed.


I actually saw a video about SmartAoE just yesterday.
I haven't installed it yet, but definetly will, so that's good to know, thank you very much for all the advice and the great APIs as well! ^^



The Aaron said:

Ok, here's a first pass at a script to do this:

If you don't want the bar, just change SHOW_BAR=true to SHOW_BAR=false on line 4.

Code:

on('ready',()=>{

  const BAR = 3;
  const SHOW_BAR=true;
  const SHOW_SUMMARY=true;

  const bound = (v, max, min) => Math.min(max,Math.max(min,v));
  const times = (n,f) => Array(n).fill(n).map(f);

  const BARLEN = 10;
  const shade = "▓";
  const lcap = "╞";
  const line = "═";
  const rcap = "╡";

  const ShadePart = (n) => times(n,()=>shade).join('');
  const LinePart = (n) => times(n,()=>line).join('');

  const GetBarLine = (v) => {
    let barLen = Math.floor(BARLEN*bound(v,1.0,0));
    let hpBar = `${ShadePart(barLen)}${LinePart(BARLEN-barLen)}`;
    if(v<1.0){
      hpBar = `${hpBar.slice(0,-1)}${rcap}`;
    }
    if(v<=0.0){
      hpBar = `${lcap}${hpBar.slice(1)}`;
    }
    return hpBar;
  };

  const GetSummaryLine = (v) => 
    ( v>=1.0 )  ? "Full Health" : 
    ( v>0.5  )  ? "Wounded"     : 
    ( v>0.0  )  ? "Bloodied"    : 
                  "Unconcious"  ;

  const UpdateTooltipOnToken = (t) => {
    let ratio = (parseFloat(t.get(`bar${BAR}_value`))||0)/(parseFloat(t.get(`bar${BAR}_max`))||1);
    let lines = [];
    if(SHOW_BAR){
      lines.push(GetBarLine(ratio));
    }
    if(SHOW_SUMMARY){
      lines.push(GetSummaryLine(ratio));
    }
    if(lines.length){
      t.set({
        show_tooltip: true,
        tooltip: lines.join(' ')
      });
    }
  };
  
  on('change:graphic',(t,p)=>{
    if(t.get('represents')!==''){
      if(
        (t.get(`bar${BAR}_value`)!== p[`bar${BAR}_value`]) ||
        (t.get(`bar${BAR}_max`)!== p[`bar${BAR}_max`])
      ){
        UpdateTooltipOnToken(t);
      }
    }
  });

  on('add:graphic',(t,p)=>{
    if(t.get('represents')!==''){
      UpdateTooltipOnToken(t);
    }
  });

  const applyToTokens = ()=>{
    let tokens = findObjs({type:'graphic'}).filter(t=>t.get('represents')!=='');
    const burndown = ()=>{
      let t = tokens.shift();
      if(t){
        UpdateTooltipOnToken(t);
        setTimeout(burndown,0);
      } else {
        log('All tokens have updated tooltips.');
      }
    };
    burndown();
  }

  applyToTokens();
});


Oh wow, that's awesome! :O
Thank you so much, works like a charm! And thank you very much for all the other scripts as well, TokenMod has been a life saver recently ^^



timmaugh said:

You're getting some good advice & suggestions, Ferry... I promised I would provide a metascript example, so maybe this can fold in with another suggestion (metascript constructions can work in the command lines of other scripts... keep reading, you'll see how). To do this, I'll use Muler  and ZeroFrame.

Step 1: Construct Mule

Muler uses abilities (called "Mules") on source characters to provide a static table that can be accessed for specific row. In your situation, you're asking for 4 entries. I'm going to create a Mule called "TipStatus" on a character called "MuleBoy", and make the entries like this:

>=100=Full Health
51-99=Wounded
>0=Bloodied
<=0=Unconscious

...which might read a little easier if I clean up the spacing (note, don't enter it as I'm about to show; enter it as above, with no extra spaces at the start or around the = sign)...

>=100   = Full Health
51-99 = Wounded
>0 = Bloodied
<=0 = Unconscious

Save that Mule (ability), and make sure that anyone who will need to access that table of information has explicit rights to the character (there's a bug currently with granting rights to "All Players").

Step 2: Usage

We're going to use the percentage of the current HP against the table (Mule) you just created. To get the percentage of HP, you're basically going to divide the current HP over the max and convert it to a percentage. There are different ways to go about it, using token bars or character attributes. I'm going to use token bars since you might want to do this for mooks:

[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]]

That will compute correctly if you have numeric values in both current and max for bar1.

As that roll sits in the command line, it will be represented by a roll marker... something like $[[0]]. We need to replace that with the value of the roll. We do that with a ZeroFrame construction, appending .value to the end of the last closing bracket:

[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]].value

In the command line, all of that will parse down to the actual percentage of HP... something like 78. So we use that against the Mule to retrieve the appropriate value:

get.MuleBoy.TipStatus.[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]].value/get

In the same way that the roll parses out to the HP percentage, all of the above will parse out to the returned text from the Mule. The above example can be put into any script's command line (with a few exceptions) to have the value "pre-retrieved" before the main script receives the message... except that we also need one more thing in the line. We need to instruct Muler to load the appropriate Mule to make the rows (variables) available:

{&mule MuleBoy.TipStatus}

That needs to go somewhere in the command line where you will want to use the get statement.

Finished Example Using Token-Mod

Using the above 2 constructions in a token-mod command line (to have it set the token-tip) might look like this:

!token-mod --on show_tooltip --set tooltip|"get.MuleBoy.TipStatus.[[floor(@{selected|bar1} * 100/@{selected|bar1|max})]].value/get"{&mule MuleBoy.TipStatus}


The caveat to this (and a few other suggestions you've received) is that this isn't automatically triggered just because the token received damage. You have to run it manually. But the benefit to this method is that you have a single macro that can be used for any token. Also, if you want to change the text or the breakpoints you don't have to change code... you just have to alter your Mule (table). Want a "Lightly Wounded" entry for 75% or better? Add a new line and your macro will automatically take it into account.

(Oh, and if you wanted to extend this to do multiple tokens at once, you could use SelectManager's forselected handle and replace the normal Roll20 constructions like @{selected|bar1} with deferred Fetch constructions... but now we're just getting jiggy in the metasauce kitchen.)


That's very interesting, I didn't know you could do such things, thank you so much! ^^ 


April 14 (3 years ago)

Edited April 14 (3 years ago)
Oosh
Sheet Author
API Scripter

DM Eddie said:

So I will just chime in and ask, did they fix the bug where tooltips would pop up in the blackness of unexplored areas when the players move their mouses over them?  Cause if they haven't, The Aaron may want to set the max health version to not report anything in a tooltip. 


Unless the character is a Ranger using their tracking ability - Immersive Mouse Minigame!