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

replicate my new Token settings to all instances of Tokens representing that Character on any Page, with token-mod ?

1745177805
Gold
Forum Champion
Hello, the Search results are proving too confusing for me to find the answer to this.  How can I replicate token-settings that I put on 1 instance of a Token (happens to be on the "Tokens" page of a module, and I happen to have used token-mod API to update all the tokens to what I want such-as color aura, nameplate settings, and bars)  ..and make those settings show-up on every Token representing each Character spread across all the different Maps on different Pages of the Game (which happens to be a module)?  Example: I updated the Vampire token on the Tokens Page to have a red aura, and red tint, and show the HP numbers on the bar. How can I make it so that every Vampire on all the other map Pages of the module will reflect those same token-settings? 
1745182995

Edited 1745205010
The Aaron
Roll20 Production Team
API Scripter
At the time of setting, you can specify a character id instead of a token id, and it will apply it to all the tokens. !token-mod --set aura1_radius|15u --ids @{Vampire|character_id} If you need to synchronize a given token with all the other tokens that represent the same character, that's a different problem, and not one you can solve with TokenMod. For that, I have another script, SyncCharacterTokens.  Select the tokens whose properties you want to sync, and run (note, you can select multiple different tokens and it will do the sync for each character): !sync-token Code: on('ready',()=>{ const syncProps=[ // graphic stuff 'isdrawing', 'flipv', 'fliph', 'tint_color', 'imgsrc', // Bars 'bar1_value', 'bar1_max', 'bar1_link','bar1_num_permission', 'bar2_value', 'bar2_max', 'bar2_link','bar2_num_permission', 'bar3_value', 'bar3_max', 'bar3_link','bar3_num_permission', 'bar_location', 'compact_bar', // auras 'aura1_radius', 'aura1_color', 'aura1_square', 'aura2_radius', 'aura2_color', 'aura2_square', // Permissions 'showplayers_name', 'playersedit_name', 'showplayers_bar1', 'showplayers_bar2', 'showplayers_bar3', 'playersedit_bar1', 'playersedit_bar2', 'playersedit_bar3', 'showplayers_aura1', 'showplayers_aura2', 'playersedit_aura1', 'playersedit_aura2', // Legacy Dynamic Lighting 'light_radius', 'light_dimradius', 'light_otherplayers', 'light_hassight', 'light_angle', 'light_losangle', // Updated Dynamic Lighting 'has_bright_light_vision', 'emits_bright_light', 'bright_light_distance', 'has_directional_bright_light', 'directional_bright_light_total', 'directional_bright_light_center', 'emits_low_light', 'low_light_distance', 'has_directional_dim_light', 'directional_dim_light_total', 'directional_dim_light_center', 'has_limit_field_of_vision', 'limit_field_of_vision_center', 'limit_field_of_vision_total', 'has_night_vision', 'night_vision_tint', 'night_vision_distance', 'has_limit_field_of_night_vision', 'limit_field_of_night_vision_center', 'limit_field_of_night_vision_total', 'night_vision_effect', 'light_sensitivity_multiplier' ]; 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; }; const simpleObj = (o) => JSON.parse(JSON.stringify(o)); const parseErrs = (err) => (err.length ? `<div><b>Errors:</b><ul>${err.map(e=>`<li>${e}</li>`).join('')}</ul></div>`: ''); const syncSiblings = (t) => { let c = getObj('character',t.get('represents')); let tokens = findObjs({ type: 'graphic', represents: c.id }).filter(o=>t.id !== o.id); let count = tokens.length; let s = (1 === count) ? '' : 's'; let src = simpleObj(t); let props = syncProps.reduce((m,p)=>({...m,[p]:src[p]}),{}); let err = []; if(props.hasOwnProperty('imgsrc')) { props.imgsrc=getCleanImgsrc(props.imgsrc); if(undefined === getCleanImgsrc(props['imgsrc'])){ delete props.imgsrc; err.push('Could not sync <code>imgsrc</code> from Marketplace.'); } } tokens.forEach(t=>t.set(props)); return `<div><code>${c.get('name')}</code>: ${count} token${s} synchronized.${parseErrs(err)}</div>`; }; on('chat:message',msg=>{ if('api'===msg.type && /^!sync-token(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){ let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); let msgs = (msg.selected || []) .map(o=>getObj('graphic',o._id)) .filter(g=>undefined !== g) .filter(g=>g.get('represents').length>0) .map(syncSiblings); sendChat('',`/w "${who}" ${msgs.join('')}`); } }); }); This is an older script, and so some of the newer properties may not be in the list to sync.  If something is missing, just let me know.
1745195062
Gold
Forum Champion
Thanks! It's working, except for I noticed.. it's missing the Token Bar Options (like bottom overlapping) and maybe the Token Bar Style (standard or compact). 
1745204828

Edited 1745205059
The Aaron
Roll20 Production Team
API Scripter
Ah yes!  I can add those.  I edited the above script to add the bar location, compact bar, bar num permissions (1, 2,3), night vision effect, and light sensitivity multiplier. Let me know if you see any others missing.
1745205130
Gold
Forum Champion
That would be a bonus, and an update, if you add those. Thank you again (for all these years)
1745209427
Gold
Forum Champion
Updated version is working  Perfectly for my needs, thanks again 
Aaron, correct me if i'm wrong. It's 1am and i havent slept yet That script applies to PC tokens only, so that, if I change something on the "PC TOKENS" on the "Combat Map page", it will reflect back to the "PC TOKENS" on the "Token Page". Thus I can copy/paste either the "Token Page" or "Map Page" to a 3rd page and everything, including status markers will be reflected to it? my issue atm is - getting status markers to continue to persist into new pages. HP is being reflected correctly but I can never get the status markers to apply to all the other copies of pc token on different pages. 
1745247454
The Aaron
Roll20 Production Team
API Scripter
No, this works on any tokens that represent a character, not just ones controlled by players.  However, it would certainly sync the status markers across to other player character tokens.  It isn't automatic, you'd need to select them and run !sync-token to do the actual work. That said, it could be extended in various ways to be automatic and apply only to PCs.
1745249627
The Aaron
Roll20 Production Team
API Scripter
(Sorry to hijack your thread, Gold!) Novercalis, here's one I wrote a few years ago that just syncs token status for player characters.  It will merge all the status markers for all the tokens representing a player token and assign them to every token that represents that player character.  For example, if you have one with Blue, and one with Green, and 3 others, all 5 will end up with Blue and Green.  It also syncs them all when you change the status markers on one, and it will sync them when TokenMod changes them.  There is also a command to sync them all if they get out of sync (changes while API isn't running, etc) !sync-all-token-status Here's the code: /* global TokenMod */ on('ready',()=>{ const isPlayerCharacter = (character) => { let players = character.get('controlledby') .split(/,/) .filter(s=>s.length); return players.includes('all') || undefined !== players.find((p)=>!playerIsGM(p)); }; const isPlayerToken = (token) => { let players = token.get('controlledby') .split(/,/) .filter(s=>s.length); if( players.includes('all') || undefined !== players.find((p)=>!playerIsGM(p))) { return true; } return isPlayerCharacter(getObj('character',token.get('represents')) || {get: function(){return '';} } ); }; const syncAllDuplicates = (obj) => { findObjs({ type: 'graphic', represents: obj.get('represents') }).forEach(o=>o.set('statusmarkers',obj.get('statusmarkers'))); }; const handleChangeGraphic = (obj,prev) => { if(isPlayerToken(obj) && obj.get('statusmarkers') !== prev.statusmarkers){ syncAllDuplicates(obj); } }; const syncAllCharacterTokens = (cid) => { let tokens = findObjs({type:'graphic', represents: cid}); let s = tokens.reduce((m,t)=> m.split(/,/).length>t.get('statusmarkers').split(/,/).length ? m : t.get('statusmarkers') ,''); if(s.length){ tokens.forEach(t=>t.set('statusmarkers',s)); } }; const syncAllStatusMarkers = (who) => { let graphics = findObjs({type: 'graphic'}); let characters = []; const processCharacters = () => { let cid = characters.shift(); if(cid){ syncAllCharacterTokens(cid); setTimeout(processCharacters,0); } else { sendChat('',`/w "${who}" <div>Synchronized all character tokens.</div>`); } }; const buildCharacterList = () => { let t = graphics.shift(); if(t){ let cid = t.get('represents'); if(cid){ characters.push(cid); } setTimeout(buildCharacterList,0); } else { characters = [...new Set(characters)]; setTimeout(processCharacters,0); } }; buildCharacterList(); }; on('chat:message',msg=>{ if('api'===msg.type && /^!sync-all-token-status(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){ let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); syncAllStatusMarkers(who); } }); on('change:graphic',handleChangeGraphic); if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(handleChangeGraphic); } });