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 or support with a custom D6 dice roller for the Hero System

Hi all, I've finally bit the bullet and gone Pro, so I could get access to the API. I have some coding experience, but not with Object Orientated languages and more specifically Java Script. I'm going to do some online tutorials, but though I might be able to jump straight in and figure things out as I went. That was a bad mistake. What I wanted to ask is if anyone has created an API that can be used to calculate damage in the Hero system. If not some guidance on creating a custom dice API would be very much appreciated. I've looked at a couple of existing one's, but the language structure is very different to what I'm used to, so I can't make a lot of sense of them..yet. The way damage works in Hero system is; Normal attack damage; Roll n  D6 Stun damage inflicted = Total number of pip's on the D6's Body Damge inflicted =  1 point per 2-5 rolled + 2 points for each 6 rolled so rolling 1,1,3,5,6 on 5D6 = 16 Stun and 4 Body Killing Attack Damage; Roll n D6 Stun Damage = (Body Damage*(1d6-1(minimum roll of 1))) Body Damage = Total number of pip's on the D6's Stun is concussion damage and Body is wounding damage. There's another Mechanic to factor in as well called Knockback, but I'd rather tackle that on a second pass. If anyone can help it would be very much appreciated. It will help speed up combat, without sacrificing mechanic's. That's fairly important to me as we Podcast our games and it will help reduce the amount of dice rolling 'segments', that I will need to edit out. 
1515191850
The Aaron
Pro
API Scripter
Which Dice API scripts have you looked at? If they're some of mine, I can probably help you understand them.
1515194985
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Another handy mechanic for Hero is the ability to sort dice in descending order, in order to handle explosions. Being able to output a descending running total of stun and body would be a nice feature. If you were thinking of a companion script to a Hero sheet, the ability to compare a result to a statistic like Ego or Pre and return the level of effect in a nice roll template would also be cool. Although the basic rolling of Hero dice is just d6s, the counting and manipulation of them is pretty complex and not really addressable with the out of the box tools of Roll20. Unfortunately, I pretty much retired from the system before 6th, so it might be different now.
1515226960

Edited 1515227027
GiGs
Pro
Sheet Author
API Scripter
Here is a bit more detail on the way damage rolls work in HERO, I'll repeat some stuff from the OP for clarity. Whenever you roll damage, you need three outcomes: the STUN, the BODY, and the Knockback (which I'll cover last). Normal Damage The simplest of the two, its always written as [x]d6, so 7d6, 13d6, etc. The STUN is the total of the dice.  BODY is 1 per die, +1 for each 6 rolled, -1 for each 1 rolled. Killing Damage This is expressed as [x]dK+[y], so 1dk+2, or 3dk-1. (It's also expressed as 1d6+1K, or 2d6K.) The y is either -1, 0, 1, or 2. It's never outside that range. (Each +3 is converted to +1dk, essentially). This time BODY is the total of the dice  STUN is BODY x a a dice roll called the STUN MULTIPLIER. In the latest edition of HERO, its 1d3; in previous editions its 1d6-1. Attacks can have a STUN MOD, this is a number that is added on to the roll. So an attack that has 2dK+1, +2 STUN, would be evaluated as follows: Roll 2d6 and add 1. That's the Body. Then find the STUN by multiplying by 1d3+2 or d6+1 (base and +2 stun) depending on which edition you are using. Knockback / Knockdown Firstly, Knockback is a system dial. Some games (typically normal hero level games) don't use Knockback at all. But superhero games almost always use it. So the dice parser would ideally have some sort of flag for knowing when to roll knockback and when to not roll it. The amount of knockback calculated by taking BODY as the starting number, and subtracting a number of d6 from it. For Normal attacks, it's usually 2d6, and for Killing attacks, its usually 3d6. But some situations or attacks change the number of dice (minimum 0), and some attacks double knockback.  So a martial arts attacker might make have an attack that does 11d6, double KB, +1d6KB. A typical attack would do 11 BODY. With Double KB, that becoems 22. Then with +1d6 KB roll, they'd roll 3d6 and reduce the 22 total by the number rolled. Say they rolled 9, the attack ends up delivering 13 units of KB. In the current edition of Champions/HERO, 1 unit of Knockback is 2 metres, so that example would be reported as 26m knockback. In all previous editions (which is relevant, because the OP seems to be using a previous edition), 1 unit of knockback is listed as inches. (1", which is a battlemap distance unit). So that example would be reported as 13" Knockback. Important:  0 Knockback is different than less then zero. If you do greater than zero knockback, that is the distance the target is moved. If you do exactly 0 KB, the target is knocked down, but doesnt move. If you do less then zero, there is no knockback effect at all. So the dice roller should differentiate between "no knockback" and "0 knockback", perhaps by calling a 0 Knockback result, "Knockdown", and just not reporting a knockback amount for less than zero. Hope that helps!
The Aaron said: Which Dice API scripts have you looked at? If they're some of mine, I can probably help you understand them. Hi Aaron, that would be Fantastic! It's the Wild Dice  and Zombie Dice scripts I've looked at. Wild Dice seems the simpler of the 2. I'm used to using Procedural Mainframe Languages like REXX. I'm finding the Javascript is harder to read as at least in some cases the operators are character's, rather than plain english. For example in Rexx an IF THEN ELSE statement if IF a Then Do b Else c End Also as it doesn't have objects only Array's there are less 'moving Parts'
1515292382
The Aaron
Pro
API Scripter
Roll20's API is pretty much entirely event driven.&nbsp; In the case of WildDice, the only event it listens to is the "chat:message" event (though it does do it's setup when the "ready" event occurs at API Sandbox startup) -- Line 169 registers the handleInput() function to be called for the "chat:message" event using the on() function. "chat:message" occurs whenever someone issues a message in the chat, be it an API command, emote, text message, roll command, whatever.&nbsp; The argument to a "chat:message" handler will be an object that represents the message.&nbsp; It has some standard properties, like "type", that you can examine to change the behavior of your script.&nbsp; Usually API scripts that listen to chat events only want to do something with messages with a type of 'api', which corresponds to lines in chat that began with ! such as "!wwd" and "!wd" for WildDice. The handleInput() function starts on line 84, here's a tour of what the lines do: 85-93 :: declare and initialize some variables (it used to be more important to do it at the top with "var" like I did here -- as of Javascript ES6, using "let" and "const" is the prefered way, and they don't need to be at the top of a function scope as they are block scoped (var is function scoped and hoisted, so declaring it deeper in the function would still hoist the definition to the beginning of the function scope). 95-97 :: check if this is an api message and early out if it isn't.&nbsp; Javascript has == and === equality operators, and corresponding != and !== inequality operators.&nbsp; You should always use === and !== until you learn what == and != really do (and then you'll realize you should never use them. =D). 99 :: get the display name of the issuing player, or "API" as the name if there wasn't an issuing player getObj() retrieves a Roll20 object given an object type ( 'player' here) and an object id ( msg.playerid here).&nbsp; You might use this function to get a graphic object, or character object or the like.&nbsp; There are also findObjs(), filterObjs(), allObjs() etc, which return collections of objects based on various criteria. || is the OR operation in Javascript.&nbsp; Unlike many other languages, the OR in Javascript doesn't return a boolean, it returns the first "truthy" value being used in the comparisons .&nbsp; In a practical sense, you can think of that as the first thing that's defined.&nbsp; So if foo, bar, and baz are 0, false, and undefined respectively, and qux is 42, this would set result to 42: let result = foo || bar || baz || qux; If getObj() returns undefined (the general "nothing to see here" value in Javascript), the result of the || will instead be the {get:()=&gt;'API'} object.&nbsp; {} in Javascript defines and Object.&nbsp; You can think of an object very much like a Hash or Associative Array from other languages.&nbsp; To the left of a : is a property of the object (get in this case), and to the right is it's value ( ()=&gt;'API' here).&nbsp; The =&gt; is called a Fat Arrow Function.&nbsp; This one defines a function that takes no arguments () and returns a string 'API'.&nbsp; Fat Arrow functions are new as of Javascript ES6 and are (mostly) just shorthand for something like function(){ return 'API'; }.&nbsp; The practical upshot of this is that the ( getObj(...) || {get: ...} ) construct will return an object guaranteed to have a property named get which is a function.&nbsp; That way, the .get('_displayname') will be valid to call on the result.&nbsp; Roll20 objects use a few functions on the object to interact with their contents. .get() and .set() are the two most common and do exactly as you imagine.&nbsp; '_displayname' is the property on a player object that contains a player's current name.&nbsp; In the case where there is no issuing player (say, the chat message was created by another API script), it will fail to find the player object and will fall back on the second half of the OR, the function that returns 'API'.&nbsp; In Javascript, passing more arguments to a function than it wants is not an error, so ()=&gt;'API' will silently ignore the '_displayname' parameter and will just return 'API', initializing the who variable.&nbsp; This will be used later to target messages for whispered output. 101 :: Split the API command message into an array.&nbsp; msg.content contains what was typed (with some special substitutions to deal with inline rolls and such) into the chat.&nbsp; All strings in Javascript have a .split() function that returns an array based on separating the string on the parameter to .split().&nbsp; /\s+/ is a regular expression that matches 1 or more whitespace characters, so this will break the command into all the words. 102 :: Switch based on the left most word of the command.&nbsp; If it's one of the cases of either '!wwd' or '!wd' the WildDice script will do something, otherwise execution will fall harmlessly out of the bottom of the function. 103-104 :: Matching the string '!wwd' and then setting w to true.&nbsp; w is initialized to false on line 86, so this is the only way it gets set to true.&nbsp; w is used to determine if the output should be whispered.&nbsp; Normally, you would have a break statement at the end of a case so that only the internals of that case get executed.&nbsp; Here, since "!wwd" should be identical to "!wd" except for setting the whisper flag, I let it fall through and execute the "!wd" case. 106 :: Match the "!wd" case to do the work of the function. 107-110 :: if there are no inline rolls (WildDice expects to be called like "!wd [[5d6]]" ), or if they called "!wd --help", then show the user the help message by calling the showHelp() function and passing it the who we determined on line 99. 112 :: _.pluck is a function from underscore.js (<a href="http://underscorejs.org" rel="nofollow">http://underscorejs.org</a>) which takes an array of objects and the name of a property and returns an array that contains the value of that property on each of the objects.&nbsp; Here I'm using that OR construct again to make sure the argument to _.pluck is an array.&nbsp; Arrays in Javascript are represented by [ ].&nbsp; In this case, it's pulling the "v" property off of each of the inline dice roll results from the message. 113 :: Another OR construct ( this is very idiomatic in Javascript) to set pips to the outcome of the subtraction (_.reduce() is another underscore.js function, best to just go read about it if you're not familiar with the reduce operation (this is basically summing rDice))&nbsp; || 0.&nbsp; I confess I have no idea what this is actually doing mathematically, you'd need to look at the inline rolls structure and figure it out (it looks like it would always be 0, but that's clearly not the case). 114 :: set the wildDie to the rightmost die by calling .pop() on the array of dice results.&nbsp; All Javascript arrays have a .push() and .pop() to treat the array like a stack by modifying the tail end (highest indexes), they also have .shift() and .unshift() that you can use to manipulate the head of the array. 115-132 :: These deal with the specific mechanics of WildDice's wild die.&nbsp; I'll gloss over this for round one. =D 133 :: using _.reduce() again to sum a list of dice.&nbsp; In this case, the rolled dice (without the wild die) along with any bonus dice (from the wild die being a 6 on line 127), plus the wild die, plus whatever pips represents. 135-162 :: creating a big block of HTML to send back to the chat with the sendChat() function.&nbsp; You see w being used with the ternary operator.&nbsp; The ternary operator is basically an inline If-Then-Else.&nbsp; ( Check ? True : False ).&nbsp; So if w is true, the message will start with '/w gm ', otherwise it will start with '/direct '.&nbsp; The rest of this block is mostly string concatenation to form the output.&nbsp; + is used for concatenating strings (which means if you have variables with numbers as strings you might get some confusing output if you don't parseInt() on them first).&nbsp; _.map() is another function from Underscore.js.&nbsp; Basically it returns an array that contains the result of calling the function in the second parameter on each of the values in the collection that is the first parameter.&nbsp; Map and Reduce are important concepts in modern programming that you can read about all over the internets. That pretty well sums up the handleInput() function. There are two useful helper functions on lines: 70-75 :: getDiceCounts(), when given an array of numbers will return an object with the numbers as the properties and the number of times it occurs as the value of each.&nbsp; &nbsp;So, this would tell you how many 6s were rolled, or how many 3s, etc. 77-82 :: getDiceArray() takes an object with numbers as the properties and counts as the values and returns an array with the numbers repeated as many times as their count.&nbsp; Effectively, these two convert between each other (though note that order is not preserved ( getDiceArray(getDiceCounts( [1,3,1,6,2] )) would probably return [1,1,2,3,6] ). Other functions: 12-32 :: The ch() function is a helper I use to output the HTML Entities for characters that would be problematic both in the source code and in the output text.&nbsp; I mainly use it in the showHelp() function to output things like &lt; and &gt; without them creating HTML.&nbsp; Since the API management is web based, using an HTML entity directly ( like &amp;gt; ) would be expanded in the code when it gets pasted in the browser, leading to the problem yet again, which is why the &amp; is assembled separately on line 29. 34-36 :: checkInstall() is a function in most of my scripts that just sets things up in the state object and does other startup tasks, as well as outputting the current version number to the API log.&nbsp; The log output is all it does here. 38-65 :: showHelp() whispers a help message to the player name passed to it on line 108. It basically explains using WildDice and consists of a&nbsp; bunch string concatenation to make an HTML message. 168-170 :: registerEventHandlers() is another function I use in many of my scripts.&nbsp; Usually, it's only registering a chat handler, but sometimes it registers other things (like 'change:graphic' handlers ). 179-184 :: an anonymous function passed directly to the on() function to register an event handler for the "ready" action.&nbsp; The "ready" action is fired once when the API Sandbox starts up, right after it has loaded all the objects in memory.&nbsp; Doing things before the "ready" event can cause weird results (like getting an "add:graphic" event call for every graphic object in the game), so it's usually a good idea to delay doing anything until the "ready" event has fired. 'use strict' is a special string that causes the Javascript interpreter to hold your code to a higher standard.&nbsp; Probably not needed in post ES6 days. 182 :: call the CheckInstall() function (an alias for checkInstall created on line 173, see below) 183 :: call the RegisterEventHandlers() function (an alias for registerEventHandlers created on line 174, see below) Most of my scripts use a construct called the Revealing Module Pattern.&nbsp; Rather than describe it again, you can read about it here: <a href="https://wiki.roll20.net/API:Cookbook#Revealing_Mod" rel="nofollow">https://wiki.roll20.net/API:Cookbook#Revealing_Mod</a>... or on the wider internet.&nbsp; Basically, line 6 creates an object called WildDice which is the result of calling an anonymous function (an IIFE -- Immediately Invoked Function Expression) which defines a bunch of functionality (which is held privately inside the function and then exposed via the aliasing object returned at the end (line 172-175).&nbsp; This works because of a concept called a Closure... see the wider internet... =D Hope that helps with the initial round of investigation.&nbsp; I'm happy to talk at length about anything javascript or API related (or pretty much anything. =D ), just ask.&nbsp; Also, there's lots of great detail in the API section of the wiki:&nbsp; <a href="https://wiki.roll20.net/API:Introduction" rel="nofollow">https://wiki.roll20.net/API:Introduction</a>
Thank you, I'll work through this over the next few day. This will be a massive help :-)
1515351784
The Aaron
Pro
API Scripter
No problem!&nbsp; Just let me know.
ok, Ive tried to write the script from scratch, using your code as an example.&nbsp; I haven't got to formatting the output yet, i'm just writing the result to chat. I got the basics working, but now I've hit an error I just cant figure out. I want to pass it !WD K [[5d]]&nbsp; or !WD N [[5d6]] and depending on the 2nd field go down one of 2 code paths. so I've coded switch(args[1]) { case 'K': w = true; sendChat(msg.who,"w=true "); break; case 'N': w = false; sendChat(msg.who,"w=false "); break; default: sendChat(msg.who,"Dropped through"); if (Boolean(w)) { tBody = pips; stun = (pips * randomInteger(6)); } else { body = getDiceCounts(rDice) tBody = (body[2] || 0) + (body[3] || 0) + (body[4] || 0) + (body[5] || 0) + ((body[6] *2) || 0); stun = pips; } But the error; TypeError: op.content.replace is not a function TypeError: op.content.replace is not a function at Object.d20.textchat.doChatInput (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:150:1), &lt;anonymous&gt;:368:29) at sendChat (/home/node/d20-api-server/api.js:1664:16) at apiscript.js:113:7 at eval (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:146:1), &lt;anonymous&gt;:65:16) at Object.publish (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:146:1), &lt;anonymous&gt;:70:8) at /home/node/d20-api-server/api.js:1510:12 at /home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:560 at hc (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:39:147) at Kd (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:546) at Id.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:489) at Zd.Ld.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:94:425) How do I identify which line of code this refers to. I'm sure it's just a silly mistake by a beginner,&nbsp; but damned if I can see it :-( &nbsp;
1516090451
GiGs
Pro
Sheet Author
API Scripter
It's hard to tell what the code is doing, since the error could be caused in the parts of the code you havent linked. It's better to post the entire code. (Tip at that on the end) One thing though: I don't think you want inline rolls in your input, because roll20 might evaluate them before they reach the API script. I'm not sure about this, but I think &nbsp;that's the way it works. In that case yYou'd need to have !WD K 5d or !WD N 5d6 and then in your code, have a dice parser to roll them It might even be simpler just to use DC, and have your code convert it to dice. You are probably going to roll each die individually (within a loop), so you can store the pip results as necessary. By the way, yYou can post the code in a prettier way. See the formatting bar across the top of posts as you are writing them? Highlight the code you've pasted, and click the leftmost button and select Code from the drop down.&nbsp; Or click the leftmode button, select code, and then paste your code into the shaded area that will appear.
1516090669

Edited 1516090776
GiGs
Pro
Sheet Author
API Scripter
Looking again at your code I notice something I'm not understanding. If Args(1) is always K or N, there's no way to reach the default section. Is that intended? Also there's no final } to end the Switch. switch(args[1]) { case 'K': w = true; sendChat(msg.who,"w=true "); break; case 'N': w = false; sendChat(msg.who,"w=false "); break; default: sendChat(msg.who,"Dropped through"); if (Boolean(w)) { tBody = pips; stun = (pips * randomInteger(6)); } else { body = getDiceCounts(rDice) tBody = (body[2] || 0) + (body[3] || 0) + (body[4] || 0) + (body[5] || 0) + ((body[6] *2) || 0); stun = pips; }
1516091061
GiGs
Pro
Sheet Author
API Scripter
By the way, Aaron, that set of explanations for your code is really helpful. I've tried looking at your scripts before, and they are way out of my league, but that is helping me start to make sense of (bits of) them. Thanks for taking the time to write that out.
1516123958

Edited 1516207867
Im using the inline roll as I want the result. Sorry I guess the name of the thread is now a bit misleading. The Default is there to catch if it wasn't picking up what I expected and the missing } is a cut and past error. Heres the whole script. You'll notice i tend to chuck in lots of sendChat's that are unnecessary for the final aim, but just help me get my head around what is going off. var rolls /* character translations */ ch = function (c) { var entities = { '&lt;' : 'lt', '&gt;' : 'gt', "'" : '#39', '@' : '#64', '{' : '#123', '|' : '#124', '}' : '#125', '[' : '#91', ']' : '#93', '"' : 'quot', '-' : 'mdash', ' ' : 'nbsp' }; if(_.has(entities,c) ){ return ('&'+entities[c]+';'); } return ''; }, /* when given an array of numbers will return an object with the numbers as the properties and the number of times it occurs as the value of each. */ getDiceCounts = function(rolls) { return ( _.reduce(rolls || [], function(m,r){ m[r]=(m[r]||0)+1; log('Parent Scope - Before call to asynchronous function.'); return m; },{})); }, /* takes an object with numbers as the properties and counts as the values and returns an array with the numbers repeated as many times as their count. */ getDiceArray = function(c) { return _.reduce(c,function(m,v,k){ _.times(v,function(){m.push(k);}); return m; },[]); }, /* Perform on Chat event */ on("chat:message", function(msg) { sendChat(msg.who,(Object.getOwnPropertyNames(inlinerolls))); /* This allows players to enter !il with a number of d6 */ if(msg.type == "api" && msg.content.indexOf("!il") !== -1) { /* testing messages sendChat(msg.who, "hello " + msg.id); sendChat(msg.who, "Mum"); sendChat(msg.who, "wibble" + msg.content); */ var args var rDice var pips var body var tBody var stun var w args = msg.content.split(/\s+/); sendChat(msg.who,"arg0= " + args[0]); sendChat(msg.who,"arg1= " + args[1]); sendChat(msg.who,"arg2= " + args[2]); switch(args[1]) { case 'K': w = true; sendChat(msg.who,"w=true "); break; case 'N': w = false; sendChat(msg.who,"w=false "); break; default: sendChat(msg.who,"Dropped through"); } rDice = _.pluck( (msg.inlinerolls && msg.inlinerolls[0].results.rolls[0].results) || [], 'v'); pips = (_.reduce(rDice,function(m,r){return m+r;},0) || 0); if (Boolean(w)) { tBody = pips; stun = (pips * randomInteger(6)); } else { body = getDiceCounts(rDice) tBody = (body[2] || 0) + (body[3] || 0) + (body[4] || 0) + (body[5] || 0) + ((body[6] *2) || 0); stun = pips; } kbDice1 = randomInteger(6) kbDice2 = randomInteger(6) /* testing messages sendChat(msg.who, "rdice" + rDice[0]); sendChat(msg.who, "rdice" + rDice[1]); sendChat(msg.who, "rdice" + rDice[2]); sendChat(msg.who, "Stun=" + pips); sendChat(msg.who, "Body=" + tBody); sendChat(msg.who, "Body2=" + body[2]); sendChat(msg.who, "Body2=" + body[3]); sendChat(msg.who, "Body2=" + body[4]); sendChat(msg.who, "Body2=" + body[5]); sendChat(msg.who, "Body2=" + body[6]); */ sendChat(msg.who,w) sendChat(msg.who, stun + " Stun " + tBody + " Body " + (tBody - (kbDice1 + kbDice2)) + " Knock Back" ) } });
1516126889

Edited 1516127561
GiGs
Pro
Sheet Author
API Scripter
You have a bunch of var statements that are missing line-ending semi-colons: var args var rDice var pips var body var tBody var stun var w Each of those lines should end with a semi-colon. All of your SendChat lines also miss the ending semi-colon. You might be better off declaring some of them (those not inside a if/switch) the first time they are assigned, like var rDice = _.pluck( (msg.inlinerolls && msg.inlinerolls[0].results.rolls[0].results) || [], 'v'); var pips = (_.reduce(rDice,function(m,r){return m+r;},0) || 0); Also it looks like you have some variables in use that haven't been declared (maybe I missed them?): kbDice1 = randomInteger(6) kbDice2 = randomInteger(6) should probably be var kbDice1 = randomInteger(6); var kbDice2 = randomInteger(6); Also inside the if statement just before the two lines above: body = getDiceCounts(rDice) needs a semi-colon ending.
Thanks, I'll rectify those. I'm used to using a Language where you don't need to declare variables in advance, an repeatedly forget. It also doesn't require an end of statment marker either.
1516186398
GiGs
Pro
Sheet Author
API Scripter
The end of line semi-colon frequently trips me up in javascript, too :) Which reminds me:
1516191109
Jakob
Sheet Author
API Scripter
You don't need &nbsp;to end lines with a semicolon in Javascript, though it's very good practice to do so. You don't even need to declare variables unless you're in strict mode (but you should always write in strict mode). Also, there's no reason to use var &nbsp;instead of&nbsp; let &nbsp;or&nbsp; const .
1516192614
GiGs
Pro
Sheet Author
API Scripter
I respect that your knowledge of javascript is vastly greater than mine. I definitely don't understand the distinction between var and let, for instance.&nbsp; If you don't need to end lines with semi-colons, why do javascript functions fail when i accidentally omit them? (Something that happens quite often, i have to admit!) Is there a way to avoid that?
1516201301

Edited 1516202689
Jakob
Sheet Author
API Scripter
I respect that your knowledge of javascript is vastly greater than mine. I definitely don't understand the distinction between var and let, for instance. var &nbsp;is scoped to the function it is contained in, which can have weird consequences when the variable is declared in the middle of the function. var test = 'strawberry'; console.log(test) // logs 'strawberry' function f(x) { console.log(test); if (true) { var test = x; console.log(test); } } f('raspberry'); // logs undefined, then 'raspberry' Essentially, every var declaration is moved (hoisted) to the top of the function it is contained in. That does not happen with let &nbsp;or const , both of which are only accessible in the block that they are in, and only after &nbsp;they have been declared. If you did the same thing with let , it would log 'strawberry', then 'raspberry', as expected. If you did the same thing with let &nbsp;but without the curly brackets around it, it would give a syntax error. If you don't need to end lines with semi-colons, why do javascript functions fail when i accidentally omit them? (Something that happens quite often, i have to admit!) Is there a way to avoid that? I don't know, automatic semicolon insertion might be playing tricks on you there, since it has to make some assumptions about where lines should end when you do not insert semicolons. But you want &nbsp;things to fail when you omit a semicolon, since it may be a sign of a more serious error. Errors are good.
OK. First GG, thanks for that. Tidying up the code did the trick. Jakob, I'm using var as I used Aaron's code as a sort of template to see how things were put together. Aaron did mention that Let and const had replaced var. I'll look at changing that now. I guess I figured var would be more forgiving, for a newbie.
1516207278
Jakob
Sheet Author
API Scripter
I'll look at changing that now. I guess I figured var would be more forgiving, for a newbie. Ultimately, it's not such a major thing, use whatever you want. But "forgiving" is usually the opposite of helpful when it comes to programming languages :).
1516215619
The Aaron
Pro
API Scripter
I'm late to the party, I see! In my defense, when I wrote that code you're looking at, let and const didn't exist yet. =D&nbsp; With the automatic semicolon insertion, the most common issue when multiple things get joined together (I pointed out to Roll20 3-4 years ago that they should join the scripts with a ';' between them and it would clear up may issues).&nbsp; With this error: TypeError: op.content.replace is not a function TypeError: op.content.replace is not a function at Object.d20.textchat.doChatInput (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:150:1), &lt;anonymous&gt;:368:29) at sendChat (/home/node/d20-api-server/api.js:1664:16) at apiscript.js:113:7 at eval (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:146:1), &lt;anonymous&gt;:65:16) at Object.publish (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:146:1), &lt;anonymous&gt;:70:8) at /home/node/d20-api-server/api.js:1510:12 at /home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:560 at hc (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:39:147) at Kd (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:546) at Id.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:489) at Zd.Ld.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:94:425) If you see that again, it's something you're passing to the sendChat(). Very likely the thing in the first parameter is not a string.&nbsp; To track that down, you can change where you're calling sendChat() to just log the parameters, something like this: //&nbsp; &nbsp; &nbsp; &nbsp; sendChat(msg.who,"arg0= " + args[0]); &nbsp; &nbsp; &nbsp; &nbsp; log([ msg.who,"arg0= " + args[0] ]); That will let you see what's getting passed in the API Console. (below where the scripts are set up)
Here's the current working code: /* Champions dice roller for Champions 5th Edition - MarkL & roll20 community! */ /* Can roll Normal, Killing or Martial damage */ /* will display dice rolled, body, stun and Knockback */ /* to use enter: !cd (damage type) [[(dice rolled)]] */ /* e.g. for normal damage: !cd n [[5d6]] */ /* for killing damage: !cd k [[4d6]] */ let rolls; /* character translations */ ch = function (c) { var entities = { '&lt;' : 'lt', '&gt;' : 'gt', "'" : '#39', '@' : '#64', '{' : '#123', '|' : '#124', '}' : '#125', '[' : '#91', ']' : '#93', '"' : 'quot', '-' : 'mdash', ' ' : 'nbsp' }; if(_.has(entities,c) ){ return ('&'+entities[c]+';'); } return ''; }, /* when given an array of numbers will return an object with the numbers as the properties and the number of times it occurs as the value of each. */ getDiceCounts = function(rolls) { return ( _.reduce(rolls || [], function(m,r){ m[r]=(m[r]||0)+1; log('Parent Scope - Before call to asynchronous function.'); return m; },{})); }, /* takes an object with numbers as the properties and counts as the values and returns an array with the numbers repeated as many times as their count. */ getDiceArray = function(c) { return _.reduce(c,function(m,v,k){ _.times(v,function(){m.push(k);}); return m; },[]); }, /* Perform on Chat event */ on("chat:message", function(msg) { /* This allows players to enter !cd with a number of d6 */ if(msg.type == "api" && msg.content.indexOf("!cd") !== -1) { /* declare values */ let args; let rDice; let pips; let body; let tBody; let stun; let w; let kb; let kbDice1 = randomInteger(6); let kbDice2 = randomInteger(6); let kbDice3 = randomInteger(6); let type; args = msg.content.split(/\s+/); switch(args[1]) { case 'M': w = true; type = "Mart"; break; case 'm': w = true; type = "Mart"; break; case 'K': w = true; type = "Kill"; break; case 'k': w = true; type = "Kill"; break; case 'N': w = false; type = "Norm"; break; case 'n': w = false; type = "Norm"; break; default: sendChat(msg.who,"Invalid Second field"); } rDice = _.pluck( (msg.inlinerolls && msg.inlinerolls[0].results.rolls[0].results) || [], 'v'); pips = (_.reduce(rDice,function(m,r){return m+r;},0) || 0); if (Boolean(w)) { tBody = pips; stun = (pips * randomInteger(6)); kb = Math.max(0,(tBody - (kbDice1 + kbDice2 + kbDice3))); } else { body = getDiceCounts(rDice); tBody = (body[2] || 0) + (body[3] || 0) + (body[4] || 0) + (body[5] || 0) + ((body[6] *2) || 0); stun = pips; kb = Math.max(0,(tBody - (kbDice1 + kbDice2))); } /* format displayed results dependant on damage type */ if (type == "Mart") { sendChat( 'Martial Damage',''+ '&lt;div&gt;'+ '&lt;div style="width: 227px; background: #d8ffdb; border: 1px solid black; padding: 1px 1px; color: black; font-weight: bold;"align=center&gt;'+ '&lt;div style="width: 209px; background: white; border: 1px solid #999999;border-radius: 1px;font-weight:bold;padding:5px 5px; margin:3px 3px;font-size: 15pt"align=center&gt;'+rDice+'&lt;br&gt;&lt;/div&gt;'+ '&lt;div style="background-color: eeeeee; font-weight: bold; color: black; line-height: 20px; padding-top: 6px; padding-left: 6px; padding-bottom: 6px; padding-right: 6px;" align=center&gt;'+ '&lt;div style="float: left; width: 31%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: blue; font-weight: bold;"align=center&gt;Stun: '+stun+' &lt;/div&gt;'+ '&lt;div style="float: left; width: 32%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: red; font-weight: bold;"align=center&gt;Body: '+tBody+' &lt;/div&gt;'+ '&lt;div style="float: left; width: 31%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: green; font-weight: bold;"align=center&gt;KB : '+kb+'&lt;/div&gt;'+ '&lt;div style="clear:both;"&gt;&lt;/div&gt;'+ '&lt;div&gt;'+ '&lt;/div&gt;'+ '&lt;/div&gt;'+ '&lt;/div&gt;'); } else if (type == "Kill") { sendChat( 'Killing Damage',''+ '&lt;div&gt;'+ '&lt;div style="width: 227px; background: #f9ffba; border: 1px solid black; padding: 1px 1px; color: black; font-weight: bold;"align=center&gt;'+ '&lt;div style="width: 209px; background: #cc0000; color: #ffffff; border: 1px solid #999999; border-radius: 1px;font-weight:bold;padding:5px 5px; margin:3px 3px;font-size: 15pt"align=center&gt;'+rDice+'&lt;br&gt;&lt;/div&gt;'+ '&lt;div style="font-weight: bold; color: black; line-height: 20px; padding-top: 6px; padding-left: 6px; padding-bottom: 6px; padding-right: 6px;" align=center&gt;'+ '&lt;div style="float: left; width: 31%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: blue; font-weight: bold;"align=center&gt;Stun: '+stun+' &lt;/div&gt;'+ '&lt;div style="float: left; width: 32%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: red; font-weight: bold;"align=center&gt;Body: '+tBody+' &lt;/div&gt;'+ '&lt;div style="float: left; width: 31%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: green; font-weight: bold;"align=center&gt;KB : '+kb+'&lt;/div&gt;'+ '&lt;div style="clear:both;"&gt;&lt;/div&gt;'+ '&lt;div&gt;'+ '&lt;/div&gt;'+ '&lt;/div&gt;'+ '&lt;/div&gt;'); } else if (type == "Norm") { sendChat( 'Standard Damage',''+ '&lt;div&gt;'+ '&lt;div style="width: 227px; background: #e6f2ff; border: 1px solid black; padding: 1px 1px; color: black; font-weight: bold;"align=center&gt;'+ '&lt;div style="width: 209px; background: white; border: 1px solid #999999;border-radius: 1px;font-weight:bold;padding:5px 5px; margin:3px 3px;font-size: 15pt"align=center&gt;'+rDice+'&lt;br&gt;&lt;/div&gt;'+ '&lt;div style="background-color: eeeeee; font-weight: bold; color: black; line-height: 20px; padding-top: 6px; padding-left: 6px; padding-bottom: 6px; padding-right: 6px;" align=center&gt;'+ '&lt;div style="float: left; width: 31%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: blue; font-weight: bold;"align=center&gt;Stun: '+stun+' &lt;/div&gt;'+ '&lt;div style="float: left; width: 32%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: red; font-weight: bold;"align=center&gt;Body: '+tBody+' &lt;/div&gt;'+ '&lt;div style="float: left; width: 31%; background: #f7f7f9; border: 1px solid black; padding: 1px 1px; color: green; font-weight: bold;"align=center&gt;KB : '+kb+'&lt;/div&gt;'+ '&lt;div style="clear:both;"&gt;&lt;/div&gt;'+ '&lt;div&gt;'+ '&lt;/div&gt;'+ '&lt;/div&gt;'+ '&lt;/div&gt;'); } } });
1516745137

Edited 1516745253
GiGs
Pro
Sheet Author
API Scripter
Great! I havent tried it yet (but i will!), but a couple of streamlining suggestions: You could store the CSS you repeat three times in variables, to make that look a little more elegant (and make it easier to change the styles). The switch cases can be replaced with: case 'M': case 'm': w = true; type = "Mart"; break; case 'K': case 'k': w = true; type = "Kill"; break; case 'N': case 'n': w = false; type = "Norm"; break; Alternatively, you could switch it to lower case first, like switch(args[1].toLowerCase()) { and then you don't need the upper case versions.
1516745940

Edited 1516747043
GiGs
Pro
Sheet Author
API Scripter
After running it, i notice two things: In the script log, i end up with a lot of messages saying: "Parent Scope - Before call to asynchronous function." I don't know what these mean or whether it is something to worry about. the script works, but seems to run a little slowly, with frequent long delays before the output appears. The other issue is that the script doesn't appear to support partial killing damage rolls (2d6+2, 3d6-1, and so on). These crop up extremely often in champions and HERO.&nbsp; I like the layout of the output. I suggest sorting the dice from highest to lowest, to make things like Explosion effects easier. Also add a space after each comma, so the numbers look less bunched together (which makes them less readable). Finally, maybe also colour 6's as green, and 1s as red, purely for aesthetics. (Though that would require tweaking the killing damage background colour).
1516749597

Edited 1516753243
GiGs
Pro
Sheet Author
API Scripter
With a little trial and error, I have found a couple of useful expressions: &nbsp; &nbsp; &nbsp; &nbsp; let expression = msg.inlinerolls[0].expression; &nbsp; &nbsp; &nbsp; &nbsp; let total = msg.inlinerolls[0].results.total; The first of the two above will report the dice string you entered. So if you rolled [[14d6]], it will give you "14d6". For [[3d6+2]] this will give you "3d6+2". The second line gives you the total of the dice, including modifiers, so will correctly calculate 3d6+2.&nbsp; It seems to me this would simplify the script somewhat. You could use total &nbsp; to quickly get the total, and not have to add the dice together. This is most handy for killing damage rolls. You still use the dice analysis parts to calculate body on normal damage, and to print out the dice report. You could use expression &nbsp;to add a title row. PS: noticed after this post that the Killing attacks do too much stun. The stun multiple is randomInteger(6) and should be randomInteger(6)-1.
Bad Boy Scott, posting before were done! &nbsp; &nbsp; Stun multiplier is sorted. Though can't just do&nbsp;randomInteger(6)-1, or even&nbsp;randomInteger(5), as it skews the probability. Went for &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Math.max((randomInteger(6) - 1),1) as it will give a result range of 1,1,2,3,4,5 &nbsp; &nbsp; We had the case of the input sorted, but lost that when swapping code. I've put that back. &nbsp; &nbsp; &nbsp; Added a third field for additional modifiers of in air, zero gravity etc. I'll look at the other suggestions as this javascript is a bit thrown together as I learn.
OH. Intend to add an explosions option.
1516761197
GiGs
Pro
Sheet Author
API Scripter
oh yes, good catch on the stun multiplier. By the way, you can delete the section of code listed below, as its not used in your macro: let rolls; /* character translations */ ch = function (c) { var entities = { '&lt;' : 'lt', '&gt;' : 'gt', "'" : '#39', '@' : '#64', '{' : '#123', '|' : '#124', '}' : '#125', '[' : '#91', ']' : '#93', '"' : 'quot', '-' : 'mdash', ' ' : 'nbsp' }; if(_.has(entities,c) ){ return ('&'+entities[c]+';'); } return ''; },
1516906806
GiGs
Pro
Sheet Author
API Scripter
Having been inspired by this thread, I've spent way too much of the last 24 hours working on my own champions damage script, to solve a couple of issues I don't see addressed here. That said, i have a few tips you might like to incorporate: replace your line pips = (_.reduce(rDice,function(m,r){return m+r;},0) || 0); with pips = msg.inlinerolls[0].results.total; this will gives you the proper total for all attacks, including killing dice rolls like 2d6+2. After this line rDice = _.pluck( (msg.inlinerolls && msg.inlinerolls[0].results.rolls[0].results) || [], 'v'); add rDice.sort(); rDice.reverse(); var printDice = rDice.join(", "); The first two lines rearrange the dice array into order, from highest to lowest. Then in each of the 3 css lines using rDice, replace it with printDice, like so: '&lt;div style="width: 209px; background: #cc0000; color: #ffffff; border: 1px solid #999999; border-radius: 1px;font-weight:bold;padding:5px 5px; margin:3px 3px;font-size: 15pt"align=center&gt;' + printDice +'&lt;br&gt;&lt;/div&gt;'+ When the results appear in chat, there will be spaced between each dice, so it will look like "6, 5, 5, 3, 1" instead of, say, "3,5,1,6,5". It looks cleaner to me, anyway.
1516908808
GiGs
Pro
Sheet Author
API Scripter
I've added my own version of the script over here:&nbsp;<a href="https://app.roll20.net/forum/post/6009891/script-champions-slash-hero-system-damage-roller/?pageforid=6009893" rel="nofollow">https://app.roll20.net/forum/post/6009891/script-champions-slash-hero-system-damage-roller/?pageforid=6009893</a>