Ok. See how this works for you. It will update player controlled tokens if you manually change them, or use TokenMod. Additionally, you can call a command to force a sync of all player controlled tokens, with the caveat that it simply finds the one with the most statuses, and syncs them all to that. Here's that command: !sync-all-token-status And 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);
}
});