Ah, good point... ok...
For any character that has a controlled by (i.e. Player characters, for the most part (Yes Scott C., this could use the PartyManager at some point... like after we've written it!)) this will create attributes named "sm_<STATUS>", so "sm_blue", "sm_snail", "sm_all-for-one", etc. Initially, they will all have a 0 value, but changing the statusmarker on a token that represents a character with a controlled by will cause the attribute to be updated to the same number.
Changing with TokenMod will similarly change the markers. (In the case of multi-markers using the [] syntax, it will basically be one of the values, not easily determined which one, so don't rely on it.)
If you have multiple tokens all representing a character, only the last change to a token will be reflected, e.g.: if you have two tokens with red:3 on them, and you take it off of one, @{selected|sm_red} will be 0 on both of them. I suggest using the --ids @{selected|character_id} on TokenMod to keep all the tokens in sync.
Script:
on('ready',() => {
const markers=[
'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', 'dead',
'skull', 'sleepy', 'half-heart', 'half-haze', 'interdiction', 'snail',
'lightning-helix', 'spanner', 'chained-heart', 'chemical-bolt',
'death-zone', 'drink-me', 'edge-crack', 'ninja-mask', 'stopwatch',
'fishing-net', 'overdrive', 'strong', 'fist', 'padlock',
'three-leaves', 'fluffy-wing', 'pummeled', 'tread', 'arrowed', 'aura',
'back-pain', 'black-flag', 'bleeding-eye', 'bolt-shield',
'broken-heart', 'cobweb', 'broken-shield', 'flying-flag',
'radioactive', 'trophy', 'broken-skull', 'frozen-orb', 'rolling-bomb',
'white-tower', 'grab', 'screaming', 'grenade', 'sentry-gun',
'all-for-one', 'angel-outfit', 'archery-target'
];
const markerAttrRegexp=/^sm_(?:red|blue|green|brown|purple|pink|yellow|dead|skull|sleepy|half-heart|half-haze|interdiction|snail|lightning-helix|spanner|chained-heart|chemical-bolt|death-zone|drink-me|edge-crack|ninja-mask|stopwatch|fishing-net|overdrive|strong|fist|padlock|three-leaves|fluffy-wing|pummeled|tread|arrowed|aura|back-pain|black-flag|bleeding-eye|bolt-shield|broken-heart|cobweb|broken-shield|flying-flag|radioactive|trophy|broken-skull|frozen-orb|rolling-bomb|white-tower|grab|screaming|grenade|sentry-gun|all-for-one|angel-outfit|archery-target)$/;
const getOrCreateAttr = (()=>{
let cache = findObjs({ type: 'attribute'})
.filter( a => markerAttrRegexp.test(a.get('name')))
.reduce( (m,a) => {
const cid=a.get('characterid');
m[cid]=m[cid]||{};
m[cid][a.get('name')]=a;
return m;
},{});
return (p)=>{
if(!cache.hasOwnProperty(p.characterid) || !cache[p.characterid].hasOwnProperty(p.name)){
cache[p.characterid]=cache[p.characterid]||{};
cache[p.characterid][p.name] = createObj('attribute',p);
}
return cache[p.characterid][p.name];
};
})();
const setStatus = (cid, s, v) => getOrCreateAttr({
type: 'attribute',
name: `sm_${s}`,
characterid: cid
}).set('current',parseInt(v,10)||0);
const handleStatusMarkerChange = _.debounce((obj,prev) => {
if(obj.get('represents').length) {
let character = getObj('character',obj.get('represents'));
if(character && character.get('controlledby').length){
let sm = obj.get('statusmarkers');
if(sm!==prev.statusmarkers){
prev.statusmarkers.split(/,/)
.map((s)=>s.split(/@/))
.forEach( s => setStatus(character.id,s[0]))
;
sm.split(/,/)
.map((s)=>s.split(/@/))
.forEach( s => setStatus(character.id,s[0],s[1]))
;
}
}
}
},10);
on('change:graphic',handleStatusMarkerChange);
/* global TokenMod */
if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){
TokenMod.ObserveTokenChange(handleStatusMarkerChange);
}
const workQueue = findObjs({ type: 'character' })
.filter( c => c.get('controlledby').length )
.reduce( (m,c) => [...m, ...markers.map( s => ({characterid: c.id, name: `sm_${s}`, current: 0}) )], []);
log(`RealizeStatusmarkers: Initializing attributes: ${workQueue.length}`);
const drainQueue = () => {
let prop = workQueue.shift();
if(prop){
getOrCreateAttr(prop);
setTimeout(drainQueue,0);
} else {
log(`RealizeStatusmarkers: Finished initializing attributes.`);
}
};
drainQueue();
});