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

Is there a way for Roll20 to recgonize newly placed tokens? (To use with TokenMod)

Hi, so I've been playing around with TokenMod and excited with the possibilities it brings to the table. So I wanted to know if there's a way to target newly placed token (persumebly having been place with a !summon script) with TokenMods The usage I thought of is mainly for cinematic purposes, supposedly, on a press of the macro, you can place a token for a spell's area of effects, then move it around using TokenMod, stuff like that.
1584627741
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
I think this is entirely possible, but I don't know of any existing script that does this.
1584663590
The Aaron
Roll20 Production Team
API Scripter
Kind of.  The API doesn't get events when other API scripts make changes, so the hookups have to be manually added between scripts.
Hmm, I have zero experience writing script so I'm a bit scare of editing existing script myself, any suggestion on where can I look to have this done?
1584735194
The Aaron
Roll20 Production Team
API Scripter
I can probably help out with that.  Can you go into a bit more detail regarding what you want to happen? Player runs a !summon critter command Summon script causes <some TokenMod command> to get run Is it always the same command, or is there a difference depending on who runs the summon command?  Is it based on the thing that was summoned?  etc
Hmm, an example scenario would be if I want to create a token for flaming sphere using a !summon script then in a later turn I want to use !tokenMod to move that created flaming sphere 30ft. north. I want there to be a way for me to write up a macro so that !TokenMod can recognize the newly created flaming sphere. Essentially what I wanted is for TokenMod to be able to use <Last Created Token> as variable to select a token that was just place on the map. I was thinking if it can be done, I can used it to create a variety of different Area of Effects moves, but what you suggest seems like you have to write a new code specifically for each ability, and I don't think I can take that much time out of you. ;v; So I think it's okay, I'll make do with what we have currently, I think I can implement my idea by having all my SFX tokens already placed on the map in GM layers beforehand and manipulate it using TokenMod that way anyway Thank you for the offer anyhow Aaron!
1584849016
The Aaron
Roll20 Production Team
API Scripter
That's actually pretty easy to mange.&nbsp; I'm assuming you're using King's Summon Script .&nbsp; If you aren't, the same changes are probably pretty easy in any other script. Here's my modified version:&nbsp; <a href="https://gist.github.com/shdwjk/2d757d5ea4c8649bfa1708feca24f6c2" rel="nofollow">https://gist.github.com/shdwjk/2d757d5ea4c8649bfa1708feca24f6c2</a> It will create a Character named Summon, with two attributes: LastId LastGroupIds You can then use those with TokenMod (or anything else) however you like. Example, summon a bard and turn him upside down: !summon bard !token-mod --set rotation|180 --ids @{Summon|LastId} Or summon a pile of dead bards to hide behind: !summon Bard 99 !token-mod --set statusmarkers|+dead --ids @{Summon|LastGroupIDs} Easy peasy!&nbsp; =D
1584859291
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Someone's been watching the Gamers: Dorkness Rising.
1584900592
The Aaron
Roll20 Production Team
API Scripter
Hehehehehehe... not recently, but it was a good example. =D
Ohh, you actually went and did it! I'll test it out! Thank you so much! ^^
It works perfectly! with this I can go on the craft cool visual effects for my players! Thank you so much again! ^^
1585008425
The Aaron
Roll20 Production Team
API Scripter
hehehe.. No problem. =D
1585010370

Edited 1585010517
The Aaron said: I hate to take a bit more of your time, but I tested it out a bit more, It work perfectly when I tested it out by writing each commands in the chatbox individually, but somehow when I try to write a Macro for it it doesn't work. The Macros does summons the correct Token though, but Token Mod doesn't actually move the token. What strange is, after the macros failed, I try copying the Token Mods commands from the macros into the chatbox and hit ENTER and it works, so Token Mod does recgonize the &lt;LastId&gt; tag that has been summoned from the Macro. So I'm not quite sure what went wrong here. When you test this out does it complete the process of summoning the Bard then turn him upside down? Here are the macro I wrote !summon Drude 1 !token-mod --set rotation|180 --ids @{Summon|LastId} --ignore-selected (Drude is the name of the Druid in my game btw)
1585011596
The Aaron
Roll20 Production Team
API Scripter
Try: !summon Drude 1 !token-mod --set rotation|180 --ids @{Summon|LastId} --ignore-selected
1585012906

Edited 1585013283
The Aaron said: Try: !summon Drude 1 !token-mod --set rotation|180 --ids @{Summon|LastId} --ignore-selected Still doesn't work, so it seem, same problem still persist, the Token gets summoned but the Token Mod commands doesn't go through. A bit more detail: I try this on two different games and it didn't work on both game it seems. I wonder if the code doesn't store the &lt;LastId&gt; tag fast enough for !Token Mod to recognize it when the Macro runs both command consecutively, so that's why it work just fine afterward? but I'm not so sure if that's how coding work, hmm
1585017155
The Aaron
Roll20 Production Team
API Scripter
OH.&nbsp; I know what the issue is and it's quite a "duh" for me.&nbsp; It's because the @{Summon|LastId} is expanded on command creation, but it doesn't get set until the first command has executed.&nbsp; I think I latched onto the " then in a later turn " and didn't think about running it at the same time as the summon. I'll see if I can come up with a solution...
1585019322

Edited 1585019372
Dumbhuman
Pro
Marketplace Creator
Disclaimer: I really don't know how dangerous the following idea is, so I'd wait for The Aaron to give an okay before using this in a game you care about. I ran across this Delay script that I've only played with a tiny amount so far, but the game I used it in (along with the tweaked version of TokenMod) was showing some strange behaviors and I'm not sure which of the few new APIs I'd installed were causing most of the problems.&nbsp; I just present it here as a possible kludgey workaround that I haven't tested at all yet, but maybe The Aaron can glean something useful from it. For some reason though, this version of the summon script doesn't work for me at all.&nbsp; I have a test game with no other scripts (thought two others had been in there previously, libTokenMarkers and PaladinAura), using two characters whose tokens are in my art library, but for one of them it gives me a message without giving me any additional tokens and for the other it crashes my API.&nbsp; I have no idea why, but I've attached a gif below showing the behavior.
1585025846
The Aaron
Roll20 Production Team
API Scripter
Yeah, I hesitate to suggest Delay as it relies on manual changes to TokenMod currently.&nbsp; I have some ideas... As for your error, it looks like the Summon script doesn't support multi-word names.&nbsp; Probably doing !summon wolf 11 wouldn't crash it?
1585081306

Edited 1585081340
I see, thank you for all your effort again, TheAaron Ah, There's some more thing I wanted to ask, just for clarification's sake Does Token Mod have a way to detect the coordinates of a targeted token? For example: If I want to move a Token to another Token location can I do something like: !token-mod --ids@{Summon|LastId} --ignore-selected --set top| Top value of &lt;target&gt;token left| left value of &lt;target&gt;token width|x height|y Also, does Token Mod recognize mathematical formula? For example: I want to move a token 1 grid upward, is it possible for me to do something like this: !token-mod --ids@{Summon|LastId} --ignore-selected --set top| top value of &lt;LastId&gt; + 70 left| left value of &lt;LastId&gt; + 70 width|x height|y
1585081817
The Aaron
Roll20 Production Team
API Scripter
You can't pull the coordinates from another token, but I do like that idea.... You can use relative numbers by prefacing with a - + / *&nbsp; (in fact, you need to preface with = to set a negative value) You can also use various units: !token-mod --set left|-1u top|+3g scale|25ft Check the help for details: !token-mod --help
1585087566
Dumbhuman
Pro
Marketplace Creator
The Aaron said: Yeah, I hesitate to suggest Delay as it relies on manual changes to TokenMod currently.&nbsp; I have some ideas... As for your error, it looks like the Summon script doesn't support multi-word names.&nbsp; Probably doing !summon wolf 11 wouldn't crash it? No such luck for me... !summon wolf 11 crashes the sandbox too.&nbsp; I just installed the version of the Summon script that I use in my regular game and it functions as expected. !summon wolf !summon wolf 11 !summon sir doubledown !summon sir doubledown 11 All the above commands work as expected, though if you haven't assigned a library image as the avatar to the character sheet then you'll get an error message along the lines of: (From KC . (GM)): Monster of name "sir doubledown" has a marketplace image or is missing. Anyway, this is the code of the base version that works for me for whatever reason, just in case you're curious about yet another mystery to be solved: /* 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-au" rel="nofollow">https://app.roll20.net/forum/post/466778/scipt-au</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. */ /* global TokenNameNumber */ on('ready',function(){ const getCleanImgsrc = function (imgsrc) { var parts = imgsrc.match(/(.*\/images\/.*)(thumb|med|original|max)([^?]*)(\?[^?]+)?$/); if(parts) { return parts[1]+'thumb'+parts[3]+(parts[4]?parts[4]:`?${Math.round(Math.random()*9999999)}`); } return; }, processCreated = (( 'undefined' !== typeof TokenNameNumber &amp;&amp; TokenNameNumber.NotifyOfCreatedToken ) ? TokenNameNumber.NotifyOfCreatedToken : _.noop ) , summonToken = function(character,pageid,baseX,baseY,baseW,baseH){ "use strict"; const page=getObj('page',pageid); baseW=baseW||70; baseH=baseH||70; character.get('defaulttoken',function(defaulttoken){ const dt = JSON.parse(defaulttoken); if(dt &amp;&amp; getCleanImgsrc(dt.imgsrc)){ dt.imgsrc=getCleanImgsrc(dt.imgsrc); dt.left=((baseX+baseW+dt.width)&gt;page.get('width') ? baseX-dt.width : baseX+baseW); dt.top=((baseY+baseH+dt.width)&gt;page.get('height') ? baseY-dt.width : baseY+baseH); dt.pageid = pageid; processCreated(createObj('graphic',dt)); } else { sendChat('','/w gm Cannot create token for &lt;b&gt;'+character.get('name')+'&lt;/b&gt;'); } }); }; 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(/\s+/); // 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') { 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) { 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') { 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 (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 = getCleanImgsrc(list[0].get('avatar')); // Extract character image URL from character sheet var character; if (!characterImage) { sendChat(msg.who, "/w gm Monster of name \"" + inputName + "\" has a marketplace image or is missing. &lt;code&gt;"+list[0].get('avatar')+"&lt;/code&gt;"); // Error message } else { 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: "hitpoints", _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 SP = findObjs({ _type: "attribute", name: "speed", _characterid: characterId },{caseInsensitive: true})[0]; //finds the speed from charactersheet if (typeof SP == 'undefined') { SP = "-1"; } else { SP = (SP) ? SP.get("current") : 0; // Changes SP to useable string } var AC = findObjs({ _type: "attribute", name: "armorclass", _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") { character = getObj('character',characterId); if(character){ summonToken( character, tok.get('pageid'), tok.get('left'), tok.get('top'), tok.get('width'), tok.get('height') ); } else { sendChat('','/w gm No character for id: &lt;b&gt;'+characterId+'&lt;/b&gt;'); } } else { character = getObj('character',characterId); if(character){ summonToken( character, tok.get('pageid'), tok.get('left'), tok.get('top'), tok.get('width'), tok.get('height') ); } else { sendChat('','/w gm No character for id: &lt;b&gt;'+characterId+'&lt;/b&gt;'); } } } }); 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(); } // sendChat(msg.who, GM + action + amount + " " + inputName + "!"); // Announces how many and what Summoner has summoned. } } } }); });
1585091440
The Aaron
Roll20 Production Team
API Scripter
Interesting.&nbsp; Apparently that's another version of the script I modified at some point... =D
1585095090
Dumbhuman
Pro
Marketplace Creator
Yeah, that was the one made to work with TokenNameNumber.&nbsp; In a perfect world, there would be one that works with TokenNameNumber, MonsterHitDice, and TokenMod commands at the summoning event!&nbsp; ;)
The Aaron said: You can't pull the coordinates from another token, but I do like that idea.... You can use relative numbers by prefacing with a - + / *&nbsp; (in fact, you need to preface with = to set a negative value) You can also use various units: !token-mod --set left|-1u top|+3g scale|25ft Check the help for details: !token-mod --help Ah, I see! Glad to know that some of it is possible! Yeah, I think a function that can pull coordinates from a token is real sweet, you can do stuff like create a visual effects tokens right on top of a monster token that you're hitting for example! ^^