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]Need help tinkering with GM-King's Summoner API

1572635794

Edited 1572635809
Mino
Marketplace Creator
With Animated WebM being a thing that can be used on the tabletop, I created a bunch of effects that I can drop on the tabletop as a token. For instance, I've made a critical hit animation that I can drop as a token overlaying other tokens. Ideally, I wanted to use GM-King's Summoner script in order to select the token I want the animated token to appear over, and summon it immediately over them. Using the script, there's a few problems. 1. The token appears to the right side of the character. 2. The animation doesn't play as it normally would when dropped onto the tabletop, and is instead stuck on the first frame. I CAN hit the Play button to continue the animation, but that defeats the purpose of automating it with an API. 3. There's a chat message declaring that it was summoned. There is a health bar above the token that was cropped out, but as far as I can tell, the giant health bar is invisible to other players and won't be an issue. I had hoped I'd be able to just tinker around with the script enough to fix things, but unfortunately, Javascript is black magic to me and I'm not sure where to change things. I tried to control+f through the code, but I don't even know what I'm looking for. Any help or pointers in the right direction would be appreciated!
1572636929
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Welcome to API scripting on Roll20 Rainy, Some of these issues are fixable, but #2 is unfortunately a limitation of the current implementation of the API-token interface. I'd recommend lending your vote support to the suggestion to implement all recent features as api interactable features . I'll answer your questions in order. Rainy said: 1. The token appears to the right side of the character. This is because the animated token is a set size that does not adjust as the token animates. So, the center of the token is located correctly, it's just that the animation isn't playing to animate across the token. 2. The animation doesn't play as it normally would when dropped onto the tabletop, and is instead stuck on the first frame. I CAN hit the Play button to continue the animation, but that defeats the purpose of automating it with an API. As I said above, currently the API cannot interact with the animation controls of tokens. Additionally, the API can't interact with some other new features of tokens like health bar placement. 3. There's a chat message declaring that it was summoned. This is caused by the sendChat() call on line 369: sendChat(msg.who, GM + action + amount + " " + inputName + "!"); // Announces how many and what Summoner has summoned. Just delete that line, and you'll be good. sendChat is the Roll20 function that outputs to the chat panel. There is a health bar above the token that was cropped out, but as far as I can tell, the giant health bar is invisible to other players and won't be an issue. You can remove this by adding the isdrawing property to the createObj calls, like these starting on line 317: createObj("graphic",  { represents: characterId,  // Links new token to charactersheet left: tok.get("left")+70, top: tok.get("top"), width: 70+size1, height: 70+size2, name: characterName, showname: true, showplayers_name: true, showplayers_bar1: true, pageid: tok.get("pageid"), imgsrc: characterImage, layer: "objects",                  isdrawing: true //Sets the token to be a drawing which hides all the token UI elements }); } else { createObj("graphic",  { represents: characterId,  // Links new token to charactersheet left: tok.get("left")+70, top: tok.get("top"), width: 70+size1, height: 70+size2, name: characterName, bar2_value: AC, bar3_value: HP, bar3_max: HP, showname: true, showplayers_name: true, showplayers_bar1: true, pageid: tok.get("pageid"), imgsrc: characterImage, layer: "objects",                  isdrawing: true //As above }); } I hope these help. You may also find the wiki on the API useful. - Scott
1572641274
Mino
Marketplace Creator
Thanks for the help! The chat fix worked, and I put my vote in for the suggestion you listed. However, I'm having a couple other issues. For one, the animation in the first example's center is not actually located correctly. I should've clarified that the animation is actually a 3x3 grid. The spark starts in the center of this grid, then moves over to where the C is. Looking at the summoned token from a similar frame as in my original example image... Essentially, the entire animation needs to be moved one square over to the left. When I went to do the health bar fix, I got a  SyntaxError: Unexpected identifier  This is how I inserted the fix into the code, please let me know if I did anything wrong. Also, there's one more problem I somehow forgot to mention in the original post, but the animation also spawns behind the targeted token. I was looking through the Wiki you linked me to and found the toFront Global Function which should work, as I'm able to take the spawned token and manually use the To Front option, but I have no idea where in the code I should put it. Still, thanks for your help up to this point!
1572643542
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Ah, ok. Thanks for the explanation on the animation. I had misunderstood from the initial images. The first two issues are easy to fix. The error is because you need a comma after the layer declaration. Compare my example code to your actual code. For the token centering, just remove the +70 from the left declaration. For the behind the token issue, I'm not sure why that's happening. I'm on mobile at the moment, so can't troubleshoot.
1572650228
Mino
Marketplace Creator
Well, okay, weird story. I did what you suggested, corrected the code, and it worked. Then, later, it just stopped working. What it's doing now is it actually behaves like a drawing in that it doesn't have grid snap or any UI elements popping out of it, but it still retains the bar and nameplate it spawns with. I've tried restarting my browser, making another backup of a campaign that never messed with this script,&nbsp; and even tried with a brand new script. Same problem across the board. I've no idea why it suddenly messed up. Just to be sure, here's the script exactly as I have it in the API window. /* This is an improved Summoning API that was built upon. Original author: Brandon W. Original here: <a href="https://app.roll20.net/forum/post/466778/scipt-auto-create-a-token#post-466778" rel="nofollow">https://app.roll20.net/forum/post/466778/scipt-auto-create-a-token#post-466778</a> Special thanks to Brian for helping me with some of the script. I hope this serves others well, do not hesistate to contact me regarding bugs or improvements. -GM King Usage: 1. Select token you want the monster to appear next to. 2. In chat, type "!summon (name of monster) (number to summon)" without brackets or quotation marks. 3. By default the monster will take up a 5x5 ft square (medium or small creature). To summon larger creatures add a letter to the end of the message: F= Fine, D = Diminutive, T= Tiny, L = large, H = Huge, G = Gargantuan, C = Colossal, cust = customised size (this tag must be followed by two numbers, see examples). 4. You can also summon a torch that will give 40 ft of vision with 20 ft of dim light (torch must be in the name). NB. The name of the monster must be in your character sheet list and the image must be the token image you wish to use (marketplace images do not work). Examples: !summon torch !summon eagle !summon eagle 2 !summon eagle 1d3 !summon eagle 1d3+1 !summon cave troll 1d3+1 H !summon flame sphere cust 140 140 NB. When making a custom size every 70 = 1 square. */ on("chat:message", function(msg) { if (msg.type == "api" &amp;&amp; msg.content.indexOf("!summon ") === 0) { var GM = ""; var size1 = 0; var size2 = size1; var s = 0; var inputName = ""; var howMuch = 0; var amount = 0; // Amount of summoned monsters. var mod = 0; // Modifier to amount. var skip = false; var action = "Summons "; var section = msg.content.split(" "); // Breaks API call into sections. if (section.length &gt; 1) { for (var j=1;j&lt;section.length;j++) { if (typeof parseInt(section[j]) === 'number' &amp;&amp; Math.round(parseInt(section[j])) % 1 == 0 || section[j].length &lt;= 4 &amp;&amp; section[j].toLowerCase().search("d") != -1 &amp;&amp; /\dd/.test(section[j]) || section[j].toLowerCase() == 'cust') { if (section[j].toLowerCase() == 'cust') { var howMuch = j; break; } else { if (j != section.length-1) { for (var m = j+1; m &lt; section.length; m++) { if (typeof parseInt(section[m]) === 'number' &amp;&amp; Math.round(parseInt(section[m])) % 1 == 0 || section[m].length &lt;= 4 &amp;&amp; section[m].toLowerCase().search("d") != -1 &amp;&amp; /\dd/.test(section[m])) { skip = true; } else { skip = false; } } } else { skip = false; } if (skip == false) { var howMuch = j; break; } } } } if (howMuch &gt; 0) { for (var k=1;k&lt;howMuch;k++) { if (k == howMuch-1) { inputName = inputName + section[k]; } else { inputName = inputName + section[k] + " "; } } if (section[howMuch].toLowerCase().search("d") != -1) // If user chose to roll. { if (section[howMuch].indexOf("+") != -1) { mod = section[howMuch].split("+"); // If user specified a modfier. section[howMuch] = mod[0]; mod = parseInt(mod[1],10); } var count = 0; var diceRoll = section[howMuch].split("d"); diceRoll[0] = diceRoll[0].replace(/\D/g,''); diceRoll[1] = diceRoll[1].replace(/\D/g,''); var lowEnd = parseInt(diceRoll[0]); var highEnd = parseInt(diceRoll[1]); while (count &lt; lowEnd) { amount = amount + randomInteger(highEnd); // Rolls dice. count++; log("Summoner: rolls a " + amount) } } else if (section[howMuch].toLowerCase() != 'cust') { var amount = parseInt(section[howMuch],10); // Creates variable for number of summoned monsters. } else { amount == 1; } amount = amount + mod; } else if (howMuch == 0) { if (section[section.length-1].length == 1) { s = 1; howMuch = section.length - 2; } amount = 1; for (var k=1;k&lt;section.length-s;k++) { if (k == section.length-1-s) { inputName = inputName + section[k]; } else { inputName = inputName + section[k] + " "; } } } if (section.length &gt; (howMuch+1) &amp;&amp; section[howMuch].toLowerCase() != "cust") { if (section[howMuch+1].toLowerCase() == "f") { size1 = -63; size2 = size1; } else if (section[howMuch+1].toLowerCase() == "d") { size1 = -56; size2 = size1; } else if (section[howMuch+1].toLowerCase() == "t") { size1 = -35; size2 = size1; } else if (section[howMuch+1].toLowerCase() == "l") { size1 = 70; size2 = size1; } else if (section[howMuch+1].toLowerCase() == "h") { size1 = 140; size2 = size1; } else if (section[howMuch+1].toLowerCase() == "g") { size1 = 210; size2 = size1; } else if (section[howMuch+1].toLowerCase() == "c") { size1 = 280; size2 = size1; } else if (section[howMuch+1].toLowerCase() == "cust") { if (typeof section[howMuch+2] != 'undefined') { size1 = parseInt(section[howMuch+2]); } else { sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } if (typeof section[howMuch+3] != 'undefined') { size2 = parseInt(section[howMuch+3]); } else { sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } } } else if (section[howMuch].toLowerCase() == "cust") { if (typeof section[howMuch+1] != 'undefined') { size1 = parseInt(section[howMuch+1]); } else { sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } if (typeof section[howMuch+2] != 'undefined') { size2 = parseInt(section[howMuch+2]); } else { sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } } } else { sendChat(msg.who, "/w gm No monsters specified to summon"); // If user writes too many things. } var check = findObjs({ _type: "character", name: inputName },{caseInsensitive: true})[0]; if (typeof check == 'undefined') { sendChat(msg.who, "/w gm Monster of name \"" + inputName + "\" does not exist."); // If monster is not in character list. } else { var list = findObjs({ _type: "character", name: inputName},{caseInsensitive: true}); var characterId = list[0].id; // Extract character ID from character sheet var characterName = list[0].get('name'); // Extract character name from character sheet var characterImage = list[0].get('avatar'); // Extract character image URL from character sheet if (characterImage.indexOf("marketplace") != -1) { sendChat(msg.who, "/w gm Monster of name \"" + inputName + "\" has a marketplace image."); // Error message } else { characterImage = characterImage.replace("med","thumb"); // Change character image to thumb if med characterImage = characterImage.replace("max","thumb"); // Change character image to thumb if max var selected = msg.selected; _.each(selected, function(obj) { var tok = getObj("graphic", obj._id); // Create variable for selected token (for summoned monster positioning) var HP = findObjs({ _type: "attribute", name: "HP", _characterid: characterId },{caseInsensitive: true})[0]; //finds the health from charactersheet if (typeof HP == 'undefined') { HP = "-1"; } else { HP = (HP) ? HP.get("current") : 0; // Changes HP to useable string } var AC = findObjs({ _type: "attribute", name: "AC", _characterid: characterId },{caseInsensitive: true})[0]; // finds the ac from charactersheet if (typeof AC == 'undefined') { AC = "-1"; } else { AC = (AC) ? AC.get("current") : 0; // Changes AC to useable string } if (amount == 0) { amount = 1; } else if (amount &gt; 20) { var tooMany = amount; amount = 20; log("Summoner: User specified " + tooMany + " monsters. Scaling to 20.") // If user spams the field. } for (var i=0;i&lt;amount;i++) { if (HP == "-1" &amp;&amp; AC == "-1" &amp;&amp; msg.content.toLowerCase().search("torch") != -1) { action = "Lights" createObj("graphic", { represents: characterId, // Links new token to charactersheet left: tok.get("left")+70, top: tok.get("top"), width: 70+size1, height: 70+size2, name: characterName, pageid: tok.get("pageid"), imgsrc: characterImage, layer: "objects", light_radius: 40, light_dimradius: 20, light_otherplayers: true }); } else if (HP == "-1" &amp;&amp; AC == "-1") { sendChat(msg.who, "/w gm HP or AC value not set (if this is a torch please include \"torch\" in name."); // Error message createObj("graphic", { represents: characterId, // Links new token to charactersheet left: tok.get("left"), top: tok.get("top"), width: 70+size1, height: 70+size2, name: characterName, showname: true, showplayers_name: true, showplayers_bar1: true, pageid: tok.get("pageid"), imgsrc: characterImage, layer: "objects", isdrawing: true //Sets the token to be a drawing which hides all the token UI elements }); } else { createObj("graphic", { represents: characterId, // Links new token to charactersheet left: tok.get("left"), top: tok.get("top"), width: 70+size1, height: 70+size2, name: characterName, bar2_value: AC, bar3_value: HP, bar3_max: HP, showname: true, showplayers_name: true, showplayers_bar1: true, pageid: tok.get("pageid"), imgsrc: characterImage, layer: "objects", isdrawing: true //Sets the token to be a drawing which hides all the token UI elements }); } } }); if (amount &gt; 1) { inputName = inputName + "s"; // If more than 1 summon, adds plural. } else if (amount == 1 &amp;&amp; action == "Lights") { amount = " a"; inputName = "torch"; } if (typeof amount === 'number') { amount = amount.toString(); } } } } }); At the very least, the center fix both works, and has stayed working.
1572656245
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hmm, looks like something's changed in token/drawing handling. I'm 90% sure that isdrawing used to hide all ui elements, not ignore the nameplate if you set it to show. Regardless, I would guess that initially your target token was meeting that first if statement: if (HP == "-1" &amp;&amp; AC == "-1" &amp;&amp; msg.content.toLowerCase().search("torch") != -1) And, actually, since you're doing a different use case for this code, you can take out all of those if if else and else that have 3 different createObjs. There's also several pieces of the code that should be cleaned up, but probably won't cause an issue. For instance, the comparisons in that if statement above use the type nonspecific comparison. Really, type specific comparison should be used (e.g. === or !==). That's probably more code clean up than you want to do, but here's a quick and dirty code edit that should do what you want: /* This is an improved Summoning API that was built upon. Original author: Brandon W. Original here: <a href="https://app.roll20.net/forum/post/466778/scipt-auto-create-a-token#post-466778" rel="nofollow">https://app.roll20.net/forum/post/466778/scipt-auto-create-a-token#post-466778</a> Special thanks to Brian for helping me with some of the script. I hope this serves others well, do not hesistate to contact me regarding bugs or improvements. -GM King Usage: 1. Select token you want the monster to appear next to. 2. In chat, type "!summon (name of monster) (number to summon)" without brackets or quotation marks. 3. By default the monster will take up a 5x5 ft square (medium or small creature). To summon larger creatures add a letter to the end of the message: &nbsp; &nbsp; F= Fine, D = Diminutive, T= Tiny, L = large, H = Huge, G = Gargantuan, C = Colossal, cust = customised size (this tag must be followed by two numbers, see examples). 4. You can also summon a torch that will give 40 ft of vision with 20 ft of dim light (torch must be in the name). NB. The name of the monster must be in your character sheet list and the image must be the token image you wish to use (marketplace images do not work). Examples: !summon torch !summon eagle !summon eagle 2 !summon eagle 1d3 !summon eagle 1d3+1 !summon cave troll 1d3+1 H !summon flame sphere cust 140 140 NB. When making a custom size every 70 = 1 square. */ on("chat:message", function(msg) { &nbsp; &nbsp; if (msg.type == "api" &amp;&amp; msg.content.indexOf("!summon ") === 0)&nbsp; { var GM = ""; var size1 = 0; var size2 = size1; var s = 0; var inputName = ""; var howMuch = 0; var amount = 0; // Amount of summoned monsters. var mod = 0; // Modifier to amount. var skip = false; var action = "Summons "; var section = msg.content.split(" "); // Breaks API call into sections. if (section.length &gt; 1){ for (var j=1;j&lt;section.length;j++){ if (typeof parseInt(section[j]) === 'number' &amp;&amp; Math.round(parseInt(section[j])) % 1 == 0 || section[j].length &lt;= 4 &amp;&amp; section[j].toLowerCase().search("d") != -1 &amp;&amp; /\dd/.test(section[j]) || section[j].toLowerCase() == 'cust'){ if (section[j].toLowerCase() == 'cust'){ var howMuch = j; break; } else{ if (j != section.length-1){ for (var m = j+1; m &lt; section.length; m++){ if (typeof parseInt(section[m]) === 'number' &amp;&amp; Math.round(parseInt(section[m])) % 1 == 0 || section[m].length &lt;= 4 &amp;&amp; section[m].toLowerCase().search("d") != -1 &amp;&amp; /\dd/.test(section[m])){ skip = true; }else{ skip = false; } } }else{ skip = false; } if (skip == false){ var howMuch = j; break; } } } } if (howMuch &gt; 0){ for (var k=1;k&lt;howMuch;k++){ if (k == howMuch-1){ inputName = inputName + section[k]; }else{ inputName = inputName + section[k] + " "; } } if (section[howMuch].toLowerCase().search("d") != -1){ if (section[howMuch].indexOf("+") != -1){ mod = section[howMuch].split("+"); // If user specified a modfier. section[howMuch] = mod[0]; mod = parseInt(mod[1],10); } var count = 0; var diceRoll = section[howMuch].split("d"); diceRoll[0] = diceRoll[0].replace(/\D/g,''); diceRoll[1] = diceRoll[1].replace(/\D/g,''); var lowEnd = parseInt(diceRoll[0]); var highEnd = parseInt(diceRoll[1]); while (count &lt; lowEnd){ amount = amount + randomInteger(highEnd); // Rolls dice. count++; log("Summoner: rolls a " + amount) } }else if (section[howMuch].toLowerCase() != 'cust'){ var amount = parseInt(section[howMuch],10); // Creates variable for number of summoned monsters. }else{ amount == 1; } amount = amount + mod; }else if (howMuch == 0) { if (section[section.length-1].length == 1){ s = 1; howMuch = section.length - 2; } amount = 1; for (var k=1;k&lt;section.length-s;k++){ if (k == section.length-1-s){ inputName = inputName + section[k]; }else{ inputName = inputName + section[k] + " "; } } } if (section.length &gt; (howMuch+1) &amp;&amp; section[howMuch].toLowerCase() != "cust"){ if (section[howMuch+1].toLowerCase() == "f"){ size1 = -63; size2 = size1; }else if (section[howMuch+1].toLowerCase() == "d"){ size1 = -56; size2 = size1; }else if (section[howMuch+1].toLowerCase() == "t"){ size1 = -35; size2 = size1; }else if (section[howMuch+1].toLowerCase() == "l"){ size1 = 70; size2 = size1; }else if (section[howMuch+1].toLowerCase() == "h"){ size1 = 140; size2 = size1; }else if (section[howMuch+1].toLowerCase() == "g"){ size1 = 210; size2 = size1; }else if (section[howMuch+1].toLowerCase() == "c"){ size1 = 280; size2 = size1; }else if (section[howMuch+1].toLowerCase() == "cust"){ if (typeof section[howMuch+2] != 'undefined'){ size1 = parseInt(section[howMuch+2]); }else{ sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } if (typeof section[howMuch+3] != 'undefined'){ size2 = parseInt(section[howMuch+3]); }else{ sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } } }else if (section[howMuch].toLowerCase() == "cust"){ if (typeof section[howMuch+1] != 'undefined'){ size1 = parseInt(section[howMuch+1]); }else{ sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } if (typeof section[howMuch+2] != 'undefined'){ size2 = parseInt(section[howMuch+2]); }else{ sendChat(msg.who, "/w gm Please ensure the \"cust\" tag is followed by two values (every 70 = 1 square)."); // If monster is not in character list. } } }else{ sendChat(msg.who, "/w gm No monsters specified to summon"); // If user writes too many things. } var check = findObjs({ _type: "character", name: inputName },{caseInsensitive: true})[0]; if (typeof check == 'undefined'){ sendChat(msg.who, "/w gm Monster of name \""&nbsp; + inputName + "\" does not exist."); // If monster is not in character list. }else{ var list = findObjs({ _type: "character", name: inputName},{caseInsensitive: true}); var characterId = list[0].id; // Extract character ID from character sheet var characterName = list[0].get('name'); // Extract character name from character sheet var characterImage = list[0].get('avatar'); // Extract character image URL from character sheet &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (characterImage.indexOf("marketplace") != -1){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(msg.who, "/w gm Monster of name \""&nbsp; + inputName + "\" has a marketplace image.");&nbsp; &nbsp; // Error message &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }else{ characterImage = characterImage.replace("med","thumb"); // Change character image to thumb if med characterImage = characterImage.replace("max","thumb"); // Change character image to thumb if max var selected = msg.selected; _.each(selected, function(obj){ var tok = getObj("graphic", obj._id); // Create variable for selected token (for summoned monster positioning) for (var i=0;i&lt;amount;i++){ &nbsp; &nbsp; let newToken = createObj("graphic", //store the created object in a variable. { represents: characterId,&nbsp; // Links new token to charactersheet left: tok.get("left"), top: tok.get("top"), width: 70+size1, height: 70+size2, pageid: tok.get("pageid"), imgsrc: characterImage, layer: "objects", isdrawing: true,//Sets the token to be a drawing which hides all the token UI elements }); if(newToken){ &nbsp; &nbsp; toFront(newToken);//if a token has been created, move it to the front. Note that toFront sometimes is not the most reliable. } } }); } } } }); Note, that I'm also pretty sure you don't need any of the code above line 180 in the code I pasted. It's all for handling dynamic summon sizes and what not. You're just making a single set image at a set size.
1572661399
Mino
Marketplace Creator
The example seems to work just fine. Thanks for the help! Hopefully by the time animations get implemented, I'll have enough of an understanding of javascript that I'll be able to just insert the control myself. But, for now, quick and dirty seems to work just fine.
1572662576
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Good to hear. Happy rolling.