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

[Help] Simple Code Issue (Comparisons)

1421430616

Edited 1421435720
So, I've been trying to modify the "bloodied" API to add what I think is a simple functionality but I can't seem to do it. I have a token with a max in bar1 of 10 and a value of, say, 8. My output log shows: "yellow false" "7" "10" I can't understand how the if statement can be evaluating to false (causing the else) when 7 is clearly less than 10, and the syntax is virtually identical to other code (the rest works). This is true on both live and dev. Please take a look and let me know what I missed, thanks! The code is as follows: on("change:graphic", function(obj) { if(obj.get("bar1_max") === "") return; if(obj.get("bar1_value") <= obj.get("bar1_max") / 2) { obj.set({ status_redmarker: true }); } else{ obj.set({ status_redmarker: false }); } // NEW CODE START if(obj.get("bar1_value") < obj.get("bar1_max")) { obj.set("status_yellow", true); log(obj.get("bar1_value")); log(obj.get("bar1_max")); } else{ obj.set("status_yellow", false); log("yellow false"); log(obj.get("bar1_value")); log(obj.get("bar1_max")); } // NEW CODE END if(obj.get("bar1_value") <= 0) { obj.set({ status_dead: true }); } else { obj.set({ status_dead: false }); } }); [Edit]: Can't find the fancy code snippit tags =( Thanks Aaron =)
I'm certainly no expert, but have you tried it with !=, instead of <, just to see if there is something else that is getting you?
1421433650

Edited 1421434111
Lithl
Pro
Sheet Author
API Scripter
"7" < "10" is not the same thing as 7 < 10. From the ECMAScript standard *, for x < y where both x and y are strings: If y is a prefix of x , return false . If x is a prefix of y , return true . Given k as the smallest nonnegative integer such that the character at position k within x is different from the character at position k in y , m is the integer that is the code unit value for the character at position k within x , and n is the integer that is the code unit value for the character at position k within y , return m < n . So, "10" is not a prefix of "7". "7" is not a prefix of "10". k is 0. m is 55. n is 49. 55 < 49 == false. Thus, "7" < "10" == false. This is all very unintuitive and hard to remember . As a solution, simply make sure to force the values to be treated as integers. There are several ways to do this: str | 0 is the fastest (IIRC), but most prone to inaccuracy +str is the simplest (IMO) parseInt(str) is the most robust, and you can even specify a radix, such as parseInt(str, 10) to make sure it's evaluated as a base-10 number instead of, say, a base-2 number ("10" base-2 is 2, while "7" base-2 is NaN) on("change:graphic", function(obj) { var current = parseInt(obj.get("bar1_value"), 10), max = parseInt(obj.get("bar1_max"), 10); if(!max) return; obj.set({ status_red: current <= max / 2, status_yellow: current < max, status_dead: current <= 0 }); }); * Note that while the presented algorithm says ToPrimitive(x, hint Number) &c., ToPrimitive on a string type returns the same string, because strings are primitives.
1421433848

Edited 1421433934
The Aaron
Pro
API Scripter
The issue is likely that obj.get('bar1_max") is returning a string. Alphabetically, the string "10\0" is less than the string "7\0", as the ASCII value of '1' (49) is less than the ASCII value of '7' (55). To get the mathematical sorting, you must parse them into integers with the parseInt() function. > "7" < "10" false > "10".charCodeAt(0); 49 > "7".charCodeAt(0); 55 > 7 < 10 true > parseInt("7",10) < parseInt("10",10) true parseInt() takes 2 arguments: string containing a number to convert base to convert from. Default is base 10, but it's a good idea to specify it anyway. BTW, if you haven't read it, I highly recommend Javascript: The Good Parts by Douglas Crockford . It's a fast read and will really help you with understanding the language and avoiding all the bad parts of it. =D
1421434018
The Aaron
Roll20 Production Team
API Scripter
Fancy code snippet tags: When posting Macros, Dice Expressions, or API Code Snippets, consider using the Code format option, available by clicking the paragraph symbol ( ¶ ) and selecting it from the list.
@Brian, if that's true, how come the other two parts work? For example, if the value is 5 or less, the red marker is enabled, and if the value is 0 or less, the red 'X' is enabled. So other string comparison values are working (I assumed it was a quirk of Javascript to convert the string values to numeric values considering it worked in the rest of the code). For both Aaron and Brian, how come the original code works but not mine? Is the other one working because the ASCII values of the number end up with the right number once the string is converted? I guess my question is how does it tell that "5" is <= ("10" / 2) but not that "7" < "10". I find myself struggling with the API because I'm familiar with VBA and Excel function syntax and there's enough differences between what the API and underscore.js syntax allows than standard Javascript I ended up running into walls. I started reading Javascript: The Good Parts a while back when I first started trying to figure out the API but found it above my skill level in basic Javascript. Either way the parseInt() function fixed the problem. I'm just kind of confused how the original code worked without it. Now onto my next project...figuring out how Javascript handles ElseIf() to see if I can make the markers exclusionary (so yellow shows when injured but above half health, only red shows at and below half health, and only the X shows at 0 or less). If I can figure that out I'll try and see if I can make the markers affect NPCs only (my players can see their own HP, no need for markers). Wish me luck =). For those who are curious as to the corrected code: if(parseInt(obj.get("bar1_value"),10) < parseInt(obj.get("bar1_max",10))) { obj.set("status_yellow", true); } else{ obj.set("status_yellow", false); }
1421437651

Edited 1421437713
The Aaron
Pro
API Scripter
The comparison operators are defined for strings, so you can compare strings with <, >, ==, !==, etc. However, you can't use division, multiplication or subtraction with strings. This forces Javascript to do an implicit conversion to a numeric type. The result then of ("10" / "2") or ("10" / 2 ) will be the number 5. You can check this with the typeof operator: > typeof ("10"/2) "number" > "10"/"2" 5 > "10"*"2" 20 > "10"-"2" 8 Javascript uses the + operator for concatenation, so that becomes more confusing: > "10"+2 "102" > "10"+"2" "102" > 10+"2" "102" > 10+2 12 With the comparison operators, if either argument is a number (by type), then the other will be implicitly converted and used to compare: > "10" < 5 false > 5 < "10" true > "bob" < 5 false > 5 < "bob" false In the case that the string argument cannot be converted to a number, it will end up as the special object NaN, and the comparison will always be false. So, the short answer is that the ("10" / 2) results in a number, forcing the <= to implicitly turn the "5" into 5, resulting in the math working correctly.
1421437993
The Aaron
Pro
API Scripter
Regarding elseif, in Javascript, you would just use another if as the statement for your else block to execute: if( foo ) { doFoo(); } else if ( bar ) { doBar(); } else if ( baz ) { doBaz(); } else { doQux(); } Since the { } are optional, this works and looks reasonable to some people. What's actually happening is this: if( foo ) { doFoo(); } else { if ( bar ) { doBar(); } else { if ( baz ) { doBaz(); } else { doQux(); } } } I tend to prefer the second syntax because it is explicit, but YMMV.
... that is good to know, about the forcing string value to a number. now, I had a script, written by someone else, somewhere, that did this pretty much as you seem to intend. I modified it, (for example, it uses bar3 for HP, not bar 1, and bar 2 for spellpoints, or fatigue (the two are tied closely in my version of the game, so it worked quite well.) I switched it to use brown marker for 1/2 life, red marker for 1/4 life, red tint for dying, and black tint for dead. it was not in any way robust though, as it seemed to break every third or fourth combat (just needed a quick reboot of the api and would be good to go again though. On a more personal note, I didn't bother seperating out the PC tokens from NPC's on this, because while a player can see their own tokens bars, I decided that with this script running they didn't need to see the rest of the parties HP bars. It worked quite well, and I didn't get any complaints from my players (none I had to take seriously anyway :P just the usual groan and moan at the start, then they were fine with it) For the record, here is the code, if that gives you any ideas (yes, its VERY messy, but it worked. once. not sure if its broken right now, to be honest, its from an older game of mine, and I stopped using it when i shifted from 3.5 to gurps, where it is unnecessary.) Note: the script locks those Markers, as they become hardwired, so you cannot use them for anything else. Also, like i said, old. uses the old status_redmarker, so hasn't been updated in that long. and also again, this WAS someone elses script, I just modified it to work with my campaign. Hope it helps somehow var deadToken = deadToken || {}; deadToken.unconscious = -1; // set to the number at which a character falls unconscious. deadToken.dyingTint = "#ff0000" // tint color of the token while unconscious. deadToken.deathValue = -10; // set to the number under 0 that a character suffers true death. deadToken.deadTint = "#000000" // the code for the tint when a token is truly dead. deadTokenCONFIG = [ {barId: 3, barRatio: .5, status: "brownmarker", whenLow: true},//lets you know when bar3 falls below 1/2 max {barId: 3, barRatio: .25, status: "redmarker", whenLow: true},//lets you know when bar3 falls below 1/4 max {barId: 2, barRatio: .5, status: "bluemarker", whenLow: true},// lets you know when bar2 falls below 1/2 max {barId: 2, barRatio: .25, status: "purplemarker", whenLow: true}];// lets you know when bar2 falls below 1/4 max on("change:token", function(obj) { deadTokenCONFIG.forEach(function(opts) { var maxValue = parseInt(obj.get("bar" + opts.barId + "_max")); var curValue = parseInt(obj.get("bar" + opts.barId + "_value")); //log(opts.barId + ": " + curValue + "/" + maxValue); if (maxValue != NaN && curValue != NaN) { var markerName = ("status_" + opts.status); if (curValue <= (maxValue * opts.barRatio) && curValue > maxValue * (opts.barRatio - .25)) { obj.set(markerName, opts.whenLow); } else { obj.set(markerName, !opts.whenLow); }; // this section tracks a characters HP value reaches and goes below 0, and marks them dead when they reach the death threshold }; if (obj.get("bar1_max") == "" ) { return } else { if (obj.get("bar3_value") == deadToken.unconscious + 1 ){ obj.set("status_redmarker", true); obj.set("tint_color", deadToken.dyingTint); obj.set("status_dead", false); } else if (obj.get("bar3_value") >= 1 ){ obj.set("tint_color", "transparent"); obj.set("status_dead", false); } else if (obj.get("bar3_value") <= deadToken.unconscious && obj.get("bar1_value") > deadToken.deathValue ){ obj.set("tint_color", deadToken.dyingTint); obj.set("status_redmarker", true); obj.set("status_dead", true); } else if (obj.get("bar3_value") <= deadToken.deathValue ){ obj.set("tint_color", deadToken.deadTint); obj.set("status_redmarker", false); obj.set("status_dead", true); }; } }); });
I ended up using perhaps a less elegant solution, but the if nesting wasn't giving me the results I wanted: on("change:graphic", function(obj) { if(obj.get("bar1_max") === "") return; if(parseInt(obj.get("bar1_value"),10) < parseInt(obj.get("bar1_max"),10)) { obj.set("status_yellow", true); obj.set("status_red", false); obj.set("status_dead", false); } else{ obj.set("status_yellow", false); } if((parseInt(obj.get("bar1_value"),10) <= (parseInt(obj.get("bar1_max"),10) / 2)) && (parseInt(obj.get("bar1_value")) > 0)) { obj.set("status_red", true); obj.set("status_yellow", false); obj.set("status_dead", false); } else{ obj.set("status_red", false); } if(parseInt(obj.get("bar1_value"),10) <= 0) { obj.set("status_dead", true); obj.set("status_yellow", false); obj.set("status_red", false); } else { obj.set("status_dead", false); } }) This will put a yellow marker if the hp is less than full but greater than half, red if it's equal or below half, and an 'X' if it's dead, exclusively. In face-to-face games I would remark if an enemy was "slightly injured" (over half hp), "visibly hurt" (less than half hp) or "dead" (zero hp or less). This automates the same gradient without using health bars. I also changed all the old code to use explicit values/types and the same formatting because I'm OCD like that =). I wasn't sure how to automate PC detection (I assume it would be a check to see if the token is associated with a character sheet that's assigned to a player) and figured that was way more work that it was worth. Hopefully it's useful to somebody =)
Michael, that's an interesting solution, a bit more gradient than I need though (I don't worry about negative HP unless my players tell me they're interested in interrogating an enemy, in which case I have all the exact HP values). Most of my games focus on the "simple and fun" side of RPGs because I usually play with inexperienced players. That would be handy if I were playing with people who wanted a more advanced simulation. Thanks, though, I'll look through it for ideas =)
Like I said, its just there for references sake. It worked, as numbers, with little effort on my part. I don't recommend just copy pasting it, as I said, it wasn't the best script. But it tracked the numbers accurately. most of the time, my npcs die, and I just add an extra ten damage, killing them automatically. the red shading only comes in to effect if it matters. like when its a PC that has gone down, or someone they are supposed to be rescuing, for example. And that is configurable, anyway. not as configurable as something Aaron would have written for it, but hey, I'm no Javascript Jedi.
1421442440

Edited 1421442518
The Aaron
Pro
API Scripter
I actually wrote one of these scripts at one point as well (Actually, I think I rewrote someone else's. Maybe Alex L's?): <a href="https://gist.github.com/shdwjk/baab586565149fe0fca5" rel="nofollow">https://gist.github.com/shdwjk/baab586565149fe0fca5</a>
oh, that is elegant. Like I said, mine was a hack, lol. I understand what yours is doing, I just have no idea HOW it is doing it :P also, that is the simplest temp_hp management I've ever seen. I like it. one suggestion: have it bind to a bar if it doesn't find a temp_hp number - that way, you can track random npcs that don't have sheets (spur of the moment creations or player summoned creatures) that get buffed (I use bar3 for temp hp anyway, so it always displays there for me, and my temp hp script reads from that, adjusting as it goes. I like yours better, mines a mess, I just like to be able to see it represented)
I actually have Aaron's, and it works very well. I disabled certain parts, since we don't use the bloodied rule. It works slick.
1421449513
Lithl
Pro
Sheet Author
API Scripter
Since everybody else is sharing, this is a similar script that I use (that somebody else wrote and I can't recall who): /** * barId - The ID of the bar to look at the values for (1, 2, or 3) * barRatio - The ratio of bar value to max value that triggers setting the * status marker (number) * status - Name of the status marker to toggle [red, blue, green, brown, * purple, dead, etc.] * whenLow - The state of the marker when the bar value is &lt;= the ratio (true * or false), default to false if omitted * exact - If `true`, `barRatio` will be treated as a number rather than a * percentage, default to false if omitted */ var statusManager = statusManager || {}; statusManager.CONFIG = [ { barId: 3, barRatio: .75, status: 'red', whenLow: true }, { barId: 3, barRatio: .25, status: 'purple', whenLow: true }, { barId: 3, barRatio: 5, status: 'black-flag', whenLow: true, exact: true }, { barId: 3, barRatio: 0, status: 'skull', whenLow: true } ]; on('change:token', function(obj) { _.each(statusManager.CONFIG, function(opts) { var maxValue = parseInt(obj.get('bar' + opts.barId + '_max')), curValue = parseInt(obj.get('bar' + opts.barId + '_value')), markerName = 'status_' + opts.status; if(curValue != NaN) { if(opts.exact) { obj.set(markerName, opts.whenLow && (curValue &lt;= opts.barRatio)); } else if (maxValue != NaN) { obj.set(markerName, opts.whenLow && (curValue &lt;= (maxValue * opts.barRatio))); } } }); }); I like this one, because it's quick and easy to change what markers happen when. You can even make markers mutually exclusive with the whenLow property. For example, if I wanted the red marker (75%) to go away at the same time the purple marker (25%) showed up, I could simply add { barId: 3, barRatio: .25, status: 'red' } to the configuration array. I modified the script a bit to add the exact option, since I was playing Unknown Ponies which has wound penalties at 75% and 25%, and unconscious at 5 HP regardless of percentage (followed by death at 0).
so THATS how I could have done it. same script as mine, but as I'm not a coder, I had to bully in the bottom half of my script, I knew there was a cleaner way to do it. the base code here is the same as the one I used (And I can't remember who wrote it originally either) but I couldn't figure out how to make it work on a set number, so i added the rest. I KNEW it wasn't the most efficient way, but it was the only way I could figure it to work.
1421465481
The Aaron
Pro
API Scripter
Michael H. said: oh, that is elegant. Like I said, mine was a hack, lol. I understand what yours is doing, I just have no idea HOW it is doing it :P also, that is the simplest temp_hp management I've ever seen. I like it. one suggestion: have it bind to a bar if it doesn't find a temp_hp number - that way, you can track random npcs that don't have sheets (spur of the moment creations or player summoned creatures) that get buffed (I use bar3 for temp hp anyway, so it always displays there for me, and my temp hp script reads from that, adjusting as it goes. I like yours better, mines a mess, I just like to be able to see it represented) It actually checks the token first on line 37, so if you changed temp_HP to bar3_vslue, it would use that bar instead. :)
1421465759

Edited 1421465999
The Aaron
Pro
API Scripter
I think it was The Dragon in this thread: <a href="https://app.roll20.net/forum/post/1418016/api-stat" rel="nofollow">https://app.roll20.net/forum/post/1418016/api-stat</a>... Nope. Eric D.: <a href="https://app.roll20.net/forum/post/138621/script-au" rel="nofollow">https://app.roll20.net/forum/post/138621/script-au</a>...