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

using _defaulttoken

1593642379

Edited 1593642437
I want to be able to pull one "party" token onto the map, then run a script that brings out the tokens of all my players' characters around the initial "party" token.  Similar to the various summon scripts, but instead of creating a new token I'd like to be able to summon the characters' default tokens with all their properties. I don't understand the _defaulttoken object. The wiki says, A JSON string that contains the data for the Character's default token if one is set. Note that this is a "blob" similar to "bio" and "notes", so you must pass a callback function to get(). Read-only. which is pretty confusing to me! Is this something I can use to do what I'm aiming for, or is it better to just create a token and set all the properties to it in the script?
1593646984
The Aaron
Roll20 Production Team
API Scripter
That's a perfect use case. The bit about bio is telling you it's an asynchronous function for the get: character.get('defaulttoken', (data) => /* do something with JSON data */ );
1593651447
The Aaron
Roll20 Production Team
API Scripter
Here's an example script (with comments) that will create the default token for a selection of characters.  You call it like: !ct --Bob the Builder --Some Name fragment --gob It will create all the matching character's default tokens (that are valid) across the top of the page, and list in chat the ones that it couldn't create because they had marketplace images. code: 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(); } }); });
Thank you!  This is just enough information to help me move further to my goal.  Now let's see what trouble I can get into.
1593663406
The Aaron
Roll20 Production Team
API Scripter
Woot!  No problem.  Let me know if you hit any rough patches. =D
What should this look like if I just use the complete name of the character, say "Bob" in the script?  Or if I have an array of the character names (or character_id)? let characters = findObjs({type:'character'}) .filter(c=>matchKey(keys,keyFormat(c.get('name'))));
1593694469
The Aaron
Roll20 Production Team
API Scripter
If you have an array of ids: let characters = charIds.map(id=>getObj('character',id)); If you have an array of complete names: let characters = findObjs({type:'character'}) .filter(c=>charNames.includes(c.get('name'))); I would prefer the character id option as character names can be changed, but ids are unique and unchanging (unless you move them to a new game). If you are making a general purpose script to release to the public, I'd suggest storing the character ids in the state object and having commands for adding characters to that list with the character name.
Thank you!  On second thought I don't want to use charIds as an array since I am running more than one campaign and your original script works just fine once I put it in a button. I want to summon the whole party from a "party token" and ping the map to that area.  It's working great now!  I just modified your script to draw above the selected token.  The party token calls the script from a token action. 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); let selected = msg.selected; if (selected===undefined){ sendChat("API","No token selected."); return; } // 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 tok = getObj("graphic",selected[0]._id); let lastX = tok.get("left")-140; let lastY = tok.get("top")-70; 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(); } }); });
1593700093

Edited 1593700450
Okay, a weird thing is happening. It resets their HP to what they had at level 1 :/ Edit:  not exactly.  2 of the three characters have level 1 hit points, the third character has level 4 hit points.  They are currently level 5. Edit:  Is it possible it's reseting the hp to the last time I set the tokens as default? Yes, that seems to be the case, as it's also changing their AC to previous numbers.  I only have AC and HP associated in the bars.
1593700399
The Aaron
Roll20 Production Team
API Scripter
It is possible.  Are all their bars correctly linked?
1593700495

Edited 1593700839
bars are linked to "hp" and "ac" When I use the script it overwrites the HP and AC on the character sheets. I removed and added the current tokens as default, and it works fine, but I can't really use it with this behavior because their HP is constantly changing.  (Hopefully their AC doesn't!)
1593704457
The Aaron
Roll20 Production Team
API Scripter
Hmm..  I've never seen that behavior before, but it makes a bit of sense based on things I've had to do with linked bars in TokenMod in the past.  I'll get you a fix for it this evening.
Thanks for all your help.  There's no rush on this.