Keith, thanks for the explanation. That makes a lot of sense. Here's the code of the version I've been using since my last change (note that it may contain unused code, I'm not great at cleaning up once I get something working). There are commented-out loggin calls to trace usage. Also note: the "height" markers is hardcoded to a specific set of elevation markers, unless you have those, stick to the conditions). // // By: Ken S // Version: 0.0.4 // Date: 21 Apr 2024 // // This script watches for changes to token status markers and reports them. Reports are made publicly, visible to all players. // // A mapping is defined for standard 5E conditions using existing tokens (this is arbitrary, although the symbols were chosen to have some // relation to the condition where possible). When a mapping exists, that is reported, otherwise the internal name is used. // // When a token with a set condition is copied and pasted, a fresh report of the status will be generated on creation of the new token copy. // // Scripted Changes: // It is a limitation of javascript that changes created programmatically (i.e. by other scripts) will not cause change events to fire. // This script uses TokenMod's reporting function to catch changes that it performs, but changes by other scripts will not be detected. // // note: internal names are not necessarily unique, and this may not work correctly with multiple markers sharing a name. // // Because markers change immediately when the floating palette is clicked on, manual changes will always show up as single updates. However, // when TokenMod is used to change two or more markers, the changes will both be reported as one event, and these will display as a list in // a single chat message. Example (presumes this is a token macro with a selected token): // // !token-mod --set statusmarkers|+bleeding-eye // // and for a dead character, which erases all other status conditions (and implicitly makes them prone): // // !token-mod --set statusmarkers|=dead|+prone // // When using TokenMod in a macro, remember to replace the pipe (vertical bar) with &#124 as pipe is a special character in macros. // // History: // 0.0.1 = 18 Oct 2023, original // 0.0.2 = 20 Oct 2023, added TokenMod support // 0.0.3 = 17 Dec 2023, changed to report using the name on the token, not the name on the sheet // 0.0.4 = 21 Apr 2024, added conditions for height markers var tokenStatus = tokenStatus || (function() { 'use strict'; // define a mapping from the tokenMarker internal name (1st value) to the text to display for it (2nd value) // this is arbitrary and for 5E conditions and some other character statuses // note that while some of these conditions imply others (eg, a paralyzed character is also incaptacitated), that rule logic is not // included here, and must be done as part of the call to TokenMod var statusNames = [ ["bleeding-eye", "condition: blinded"], ["chained-heart", "condition: charmed"], ["ninja-mask", "condition: deafened"], ["broken-skull", "condition: freightened"], ["grab", "condition: grappled"], ["interdiction", "condition: incapacitated"], ["half-haze", "condition: invisible"], ["padlock", "condition: paralyzed"], ["white-tower", "condition: petrified"], ["skull", "condition: poisoned"], ["back-pain", "condition: prone"], ["fishing-net", "condition: restrained"], ["screaming", "condition: stunned"], ["overdrive", "condition: unconscious"], ["death-zone", "status: bleeding"], ["spanner", "status: stabilized"], ["lightning-helix", "status: hasted"], ["snail", "status: slowed"], ["Up_Marker_White_5::6021461", "height: +5"], ["Up_Marker_White_10::6021467", "height: +10"], ["Up_Marker_White_15::6021461", "height: +15"], ["Up_Marker_White_20::6021470", "height: +20"], ["Up_Marker_White_25::6021473", "height: +25"], ["Up_Marker_White_30::6021476", "height: +30"], ["Up_Marker_White_35::6021479", "height: +35"], ["Up_Marker_White_40::6021481", "height: +40"], ["Up_Marker_White_45::6021484", "height: +45"], ["Up_Marker_White_50::6021487", "height: +50"], ["Up_Marker_White_55::6021490", "height: +55"], ["Up_Marker_White_60::6021493", "height: +60"], ["Up_Marker_White_65::6021495", "height: +65"], ["Up_Marker_White_70::6021498", "height: +70"], ["Up_Marker_White_75::6021501", "height: +75"], ["Up_Marker_White_80::6021504", "height: +80"], ["Up_Marker_White_85::6021506", "height: +85"], ["Up_Marker_White_90::6021509", "height: +90"], ["Up_Marker_White_95::6021512", "height: +95"], ["Up_Marker_White_100::6021458", "height: +100"] ], version = '0.0.4', lastUpdate = 1713749357, // this is Unix Epoch in milliseconds (21 April 2024) schemaVersion = 0.1, presentTime = lastUpdate, // Unix Epoch (milliseconds), will be updated at each command invocation minArgs = 3, // count of minimum command parameters excluding the command itself ch = function (c) { var entities = { '<' : 'lt', '>' : 'gt', "'" : '#39', '@' : '#64', '{' : '#123', '|' : '#124', '}' : '#125', '[' : '#91', ']' : '#93', '"' : 'quot', '*' : 'ast', '/' : 'sol', ' ' : 'nbsp' }; if( entities.hasOwnProperty(c) ){ return `&${entities[c]};`; } return ''; }, // function definitions go here // logEvent // // debug function logEvent = function(newobj, prev) { log('newobj'); log(newobj); log('prev'); log(prev); }, // logEvent // sendMsg // issue a chat log message to all players sendMsg = function(msgStr) { sendChat('DM', '&{template:desc} {{desc=' + msgStr + '.}}'); }, // sendMsg // getStatusNames // // see if there's a mapping defined, and if so return the mapped name, otherwise return the internal name getStatusNames = function(markerName, msg) { let index = []; index = statusNames.filter( function(el) { return !!!el.indexOf(markerName); }); if ((index == null) || (index.length == 0)) { msg = msg + markerName; } else { msg = msg + index[0][1]; } return(msg); }; // getStatusNames // reportStatusChange // // report to the chat log what changed reportStatusChange = function(obj, added, removed) { let msg = "Status changed: "; let i, len = 0; let newList = true; let doingBoth = false; //let index = 0; //log("in TS RSC: "); //log(statusNames); //log(obj); //log(obj.get("represents")); let char = getObj('character', obj.get('represents')); //log(char); let tokenName = obj.get('name'); //log(tokenName); if (tokenName != '') { msg = msg + tokenName + ": "; // normally report the name applied to the token } else { msg = msg + char.get('name') + ": "; // report the owning character name of the token } // append the list of added status elements i = 0; len = added.length; newList = true; while (i < len) { if (added[i] != "") { if (newList) { msg = msg + "is ("; newList = false; } else { msg = msg + ', '; } msg = getStatusNames(added[i], msg); doingBoth = true; } i++; } if (!newList) { msg = msg + ") "; } // append the list of removed status elements i = 0; len = removed.length; newList = true; while (i < len) { if (removed[i] != "") { if (doingBoth) { msg = msg + ', '; doingBoth = false; } if (newList) { msg = msg + "is no longer ("; newList = false; } else { msg = msg + ', '; } msg = getStatusNames(removed[i], msg); } i++; } if (!newList) { msg = msg + ") "; } sendMsg(msg); }, // reportStatusChange // HandleChange // Select the reporting function based on what changed. // // Keep this as lightweight as possible HandleChange = function (newobj, prev) { let newName = ''; let reporting = false; let added = []; let removed = []; //log('in TS HC: '); //logEvent(newobj, prev); if (!newobj || !prev) { log('TM: error, newobj or prev undefined.'); return; } if (newobj.get('statusmarkers') == prev.statusmarkers) return; // it didn't change, so bail // normalize the two into arrays let prevstatusmarkers = prev.statusmarkers.split(","); let statusmarkers = newobj.get('statusmarkers').split(","); //log(statusmarkers); //log(prevstatusmarkers); statusmarkers.forEach(function(newMarker){ if ((newMarker != "") && !prevstatusmarkers.includes(newMarker)) { //sendMsg("TS: added " + newMarker); added.push(newMarker); reporting = true; } }); prevstatusmarkers.forEach(function(newMarker){ if ((newMarker != "") && !statusmarkers.includes(newMarker)) { //sendMsg("TS: removed " + newMarker); removed.push(newMarker); reporting = true; } }); if (reporting) { reportStatusChange(newobj, added, removed); } }, // HandleChange // checkInstall // This code runs when the script is installed to the sandbox or on sandbox restart (game load, etc), ie not often. // Purpose is to provide a log message that the code loaded okay, and update any non-transient storage as needed. checkInstall = function () { log('-=> tokenStatus v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); log(' > no local info preserved in state <'); // no state needed at present, place code to restore here if we use it }, // checkInstall // registerEventHandlers // on chat.message: normally invoked by the user typing the command in chat // on change:attribute: invoked by a chang to an attribute on a character sheet // if we wanted to listen for other events (like changes to a token) that would go here too. registerEventHandlers = function () { //on('chat:message', HandleInput); on('change:graphic:statusmarkers', HandleChange); // function(obj, prev) // events do not fire for API-driven changes, but TokenMod supports an event reporting mechanism, so use it to catch TokenMod changes if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(HandleChange); } //TokenMod watcher /* if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(function(obj,prev){ if(obj.get('statusmarkers') !== prev.statusmarkers){ sendChat('Observer Token Change','Token: '+obj.get('name')+' has changed status markers!'); } }); } //TokenMod watcher */ }; // the following maps externally visible names (the first name) to internal functions (the second name) return { checkInstall: checkInstall, registerEventHandlers: registerEventHandlers }; }()); // on ready: When the script is loaded, these are called. (game is completely loaded by then) on("ready",function () { 'use strict'; tokenStatus.checkInstall(); tokenStatus.registerEventHandlers(); }); // on // tokenStatus and here is the macro to set a condition: ?{Add Condition|Blinded,!token-mod --set statusmarkers&#124+bleeding-eye|Charmed,!token-mod --set statusmarkers&#124+chained-heart|Deafened,!token-mod --set statusmarkers&#124+ninja-mask|Frightened,!token-mod --set statusmarkers&#124+broken-skull|Grappled,!token-mod --set statusmarkers&#124+grab|Incapacitated,!token-mod --set statusmarkers&#124+interdiction|Invisible,!token-mod --set statusmarkers&#124+half-haze|Paralyzed,!token-mod --set statusmarkers&#124+padlock&#124+interdiction|Petrified,!token-mod --set statusmarkers&#124+white-tower&#124+interdiction|Poisoned,!token-mod --set statusmarkers&#124+skull|Prone,!token-mod --set statusmarkers&#124+back-pain|Restrained,!token-mod --set statusmarkers&#124+fishing-net|Stunned,!token-mod --set statusmarkers&#124+screaming&#124+interdiction|Unconscious,!token-mod --set statusmarkers&#124+overdrive&#124+interdiction|Hasted,!token-mod --set statusmarkers&#124+lightning-helix|Slowed,!token-mod --set statusmarkers&#124+snail|Bleeding,!token-mod --set statusmarkers&#124=death-zone&#124+back-pain|Stabilized,!token-mod --set statusmarkers&#124+spanner&#124-death-zone|Dead,!token-mod --set statusmarkers&#124=dead&#124+back-pain} and to clear it: ?{Remove Condition|Blinded,!token-mod --set statusmarkers&#124-bleeding-eye|Charmed,!token-mod --set statusmarkers&#124-chained-heart|Deafened,!token-mod --set statusmarkers&#124-ninja-mask|Frightened,!token-mod --set statusmarkers&#124-broken-skull|Grappled,!token-mod --set statusmarkers&#124-grab|Incapacitated,!token-mod --set statusmarkers&#124-interdiction|Invisible,!token-mod --set statusmarkers&#124-half-haze|Paralyzed,!token-mod --set statusmarkers&#124-padlock&#124-interdiction|Petrified,!token-mod --set statusmarkers&#124-white-tower&#124-interdiction|Poisoned,!token-mod --set statusmarkers&#124-skull|Prone,!token-mod --set statusmarkers&#124-back-pain|Restrained,!token-mod --set statusmarkers&#124-fishing-net|Stunned,!token-mod --set statusmarkers&#124-screaming&#124-interdiction|Unconscious,!token-mod --set statusmarkers&#124-overdrive&#124-interdiction|Hasted,!token-mod --set statusmarkers&#124-lightning-helix|Slowed,!token-mod --set statusmarkers&#124-snail|Bleeding,!token-mod --set statusmarkers&#124-death-zone|Stabilized,!token-mod --set statusmarkers&#124-spanner|Dead,!token-mod --set statusmarkers&#124-dead} It requires the TokenMod script, obviously. The Aaron: I will PM you an invite to my test game.