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 .
×
May your rolls be merry + bright! 🎄
Create a free account

Place a token from Journal

Hi everyone. I am working on automating some spell effects in 5e campaign. My use case is summoning. Lots of pretty simple api scripts out there that will create a object that will function as a "token." However, speed of combat is very important to my players and I would like to be able to provide my wizards with some flashy stuff that doesn't require them to drop ten "objects" everytime he casts animate object. Here is where I am at so far.  I created Animate Object characters in the Journal, each has individual stats with token actions appropriate to their size  Shared these objects with all characters He can drag and drop them on the vtt without issue. He has asked me to "summon" them for him with some flashiness, in other words with an effect. I didn't expect it to be as difficult as it has turned out to be, I have a API script that creates spiritual weapon, cloud of daggers, etc. and he loves the effects.  He is a necromancer and I fully expect that he will be asking for something similar when he figures out how to make it cool with Animate Dead. If anyone has any pointers on how to accomplish this or can point me in the right direction for a script that could get me started I would really appreciate it.  
1587825360
The Aaron
Roll20 Production Team
API Scripter
I'm unclear on what you're asking for.  FX to make Animate Dead look cool?  Or something about rapidly creating lots of objects at some point?  I'm happy to try and point you in the right direction if you can clarify the questions. =D
The Aaron said: I'm unclear on what you're asking for.  FX to make Animate Dead look cool?  Or something about rapidly creating lots of objects at some point?  I'm happy to try and point you in the right direction if you can clarify the questions. =D Thanks. Sorry I sometimes tend to ramble. :) What I want to to is rapidly create objects of characters I have already created in the journal. 
Trey, You can try this script I wrote.  It is by no means anywhere as comprehensive as The Aaron's TokenNameNum, but it works for me.  It requires adding a couple of attributes ('TokenDropper' and 'TokenCount') to each character you want to drop multiple copies of onto the virtual table top, but this will allow you to turn on/off the ability on a per character basis and will also tally how many copies of the character you have dropped.  Enjoy! //TokenDropper v1.0 by Christopher Craig 20200425 // //Program Contract: //Requirement 1:  Character objects must be created using D&D 5e OGL character sheet using the NPC set of statistics. //Requirement 2:  Character objects must have default tokens created and assigned the character sheet. //Requirement 3:  Default token object defined on the character sheet must reference the correct character in it's "Represents Character" field. //Requirement 4:  Character object must have a 'TokenDropper' attribute added to it with the current value = "on" //Requirement 5:  Character object must have a 'TokenCount' attribute added with a valid number value assigned to the current value (suggest 0). //Requirement 6:  Patience! Because of the way token graphic objects are created, it was necessary to put a stalling routine after the graphic token is and  //                  before all of the properties can be assigned. // //After all requirements above are met, dragging and dropping a character entry from your journal to the virtual table top will produce the following:  //Output 1: graphic token based on the default token definition for the character object is create (normal Roll20 function). //Output 2: After a delay of 3 seconds, the token's name will be updated to include a number afterward to differentiate between the multiple copies of this token. //Output 3: The character will have hit points (health) rolled for it based on the npc_hpformula attribute of the character sheet. //Output 4: Bar 1 current and max values will be populated with the number rolled for hitpoints. //Output 5: Bar 2 will be updated with the character's armor class from the  attribute of the character sheet. //Output 6: Bar 3 will be updated with the character's speed from the  attribute of the character sheet. //Output 7: A yellow status marker will be added with the last digit of the modified token's name.  These are easier for the players to see, especially in melee  //                  combat when the nameplates show in sqaures below the graphic token. //Output 8: bar1_link, bar2_link, and bar3_link will be set to null - DO NOT LINK THESE to your character attributes!!  If you do, a change in one toke will  //                  change the stat for ALL tokens that also have that attribute linked to one of the stat bars. // // //Will only be called for new objects that get added, since existing objects have already been loaded before the ready event fires. on("ready", function() { log ("TD checkpoint 1:  Sandbox Ready");     //Watch for a new token/grahpic item     on("add:graphic", function(obj) {         //Declare and assign the character variable to the character represented by the new graphic         var objDroppedChar = getObj("character",obj.get("represents"));         //If graphic has an associated character object then continue         if (objDroppedChar!==undefined) {             //Declare and assign objTokenDropper attribute to the boolean TokenDropper attribute.             var objTokenDropper = getAttrByName(objDroppedChar.id, "TokenDropper");              if (objTokenDropper!==undefined) {                 if (objTokenDropper="on") {                     var objTokenCount = findObjs({name: "TokenCount",_type: "attribute", _characterid: objDroppedChar.id}, {caseInsensitive: false})[0];                     var numTokenCount = Number(objTokenCount.get("current"))+1;                     objTokenCount.set("current", numTokenCount);                     log('numTokenCount: '+numTokenCount);                     var strTokenName = objDroppedChar.get("name");                      strTokenName = strTokenName + " " + numTokenCount;                     log('strTokenName: '+strTokenName)                     var objHPFormula = findObjs({name: "npc_hpformula",_type: "attribute", _characterid: objDroppedChar.id}, {caseInsensitive: false})[0];                     var strHPFormula = objHPFormula.get('current');                     log('strHPFormula: '+strHPFormula);                     setTimeout(myFunction, 3000);                     function myFunction() {                         obj.set({                             name:strTokenName,                             bar1_link:null,                             bar2_link:null,                             bar3_link:null,                             bar2_value:getAttrByName(objDroppedChar.id, "npc_speed"),                             bar3_value:getAttrByName(objDroppedChar.id, "npc_ac"),                             showname:true,                             showplayers_name:true,                             showplayers_bar1:false,                             showplayers_bar2:false,                             showplayers_bar3:false,                             statusmarkers:"yellow@"+(numTokenCount%10),                         });                     };                     sendChat('','/r '+strHPFormula,function(r){                         var numHP=0;                         _.each(r,function(subr){                             var val=JSON.parse(subr.content);                             if(_.has(val,'total')) {                                 numHP+=val.total;                             }                         });                         obj.set({                             bar1_value: numHP,                             bar1_max: numHP                         });                     });                 };             };         };     }); });
1587868836
The Aaron
Roll20 Production Team
API Scripter
Ah! If you want to make a Token for a Character in the Journal it's actually pretty easy, with a minor caveat: you can't create anything that resides in the Marketplace. If you want to create graphics from the marketplace, you must download them and upload them to a user library. All the setup details for a character's default token are stored in the defaulttoken property. You must get this property with the asynchronous get(), then you can create it with createObj(), you just need to add the pageid, top, and left properties. Here's a small script that will take character name fragments and create the default tokens for all matching characters, if it can.  It will place them across the top of the screen in rows centered in each unit square, but oblivious to the actual size of the token.  Here's a command that will create a token for each character with "gob" in the name: !ct --gob Script, annotated: on('ready',()=>{ // function to grab the page a player is on, even if split from the party or a gm const getPageForPlayer = (playerid) => { let player = getObj('player',playerid); if(playerIsGM(playerid)){ return player.get('lastpage'); } let psp = Campaign().get('playerspecificpages'); if(psp[playerid]){ return psp[playerid]; } return Campaign().get('playerpageid'); }; // function to convert from any user library url to a valid thumb url for the api const getCleanImgsrc = (imgsrc) => { let 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; }; // some text matching functions for easily taking name parts and finding matches const keyFormat = (text) => (text && text.toLowerCase().replace(/\s+/g,'')) || undefined; const matchKey = (keys,subject) => subject && !_.isUndefined(_.find(keys,(o)=>(-1 !== subject.indexOf(o)))); // handle the !ct command // !ct --some name part --some other name part on('chat:message', (msg) => { if('api'===msg.type && /^!ct\s+/.test(msg.content)){ let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); let pageid = getPageForPlayer(msg.playerid); let page = getObj('page',pageid); // get each of the name parts let args = msg.content.split(/\s+--/).slice(1); // build them as matching fragments let keys = args.map(keyFormat); // find all the characters that match those fragments let characters = findObjs({type:'character'}) .filter(c=>matchKey(keys,keyFormat(c.get('name')))); // store some information about the page and where we've made tokens (see below) let lastX = -35; let lastY = 35; const pageX = page.get('width')*70; const pageY = page.get('height')*70; // getting X marches the creation point across the top of the screen, // then back to the left like a typewriter const nextX = ()=>{ lastX+=70; if(pageX<lastX){ lastX=35; lastY+=70; if(pageY<lastY){ lastY=35; } } return lastX; }; // just gets the current Y to use const nextY = ()=> lastY; // asynchronous function to create tokens const burndown = ()=>{ // while there are still characters, grab the next one if(characters.length){ let c = characters.shift(); // get the defaulttoken data, which is an asynchronous call c.get('defaulttoken',(dt) => { // decode it from JSON let props = JSON.parse(dt); // if it decoded, we'll process it if(props){ // get a cleaned up imgsrc props.imgsrc = getCleanImgsrc(props.imgsrc); // for good images, we'll make tokens if(props.imgsrc) { // add the page id, top and left properties props.pageid = pageid; props.top = nextY(); props.left = nextX(); // create the token createObj('graphic',props); } else { // for Marketplace images, report to the caller sendChat('', `/w "${who}" <div>Cannot create <code>${c.get('name')}</code> because it has a Marketplace image.</div>`); } } // defer call to the next character to process setTimeout(burndown,0); }); } }; // start working through the queue burndown(); } }); });