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

Statusinfo with a different token set . Is it possible?

Hi guys! I recently found out about  Authors:  Robin Kuiper Statusinfo API. I could really really use it in my PF2 game, but sadly it uses the Default token images which seem to be embedded directly into the code of the script. Is there a way for me to manually change these to my current PF2E only token set? they even have different names and use single letters and numerals 0 to 9 as well. If there is a way fro the script to have a way to manually add my tokens somehow and remove the default ones it would be amazing. Anyone can help?
As an extra bonus I tried to contact the original author Robin Kuiper but for some reason roll20 wont let me send him direct messages. I tried to use the free to use under cc3 tokens you can find here: <a href="https://old.reddit.com/r/Pathfinder2e/comments/g19a98/roll20_token_markers_pathfinder_2e_conditions/" rel="nofollow">https://old.reddit.com/r/Pathfinder2e/comments/g19a98/roll20_token_markers_pathfinder_2e_conditions/</a> But when I looked at the code of Status Info it seems they are hardcodded into the script. Is there a way to make the script read from the currently loaded token set? from a different source? What would I need to edit in there if anyone is willing to give me the starting points. Is it even possible? Any help will be greatly appreciated. Cheers!&nbsp;
1645497935
Andrew R.
Pro
Sheet Author
It should be doable, yes. Making it configurable for any Status Marker Library and default to the usual Status Markers would be ideal. The way ScriptCards can save and restore configuration might be a good example.&nbsp;
Tokenmod works with custom statusmarkers.&nbsp; Also, I dunno what it conflicts with, and I don't even know if this is a serious thing to care about, but whenever I installed statusinfo in my sessions it conflicted with other scripts, no idea which ones cause the error is not informative.&nbsp; My guess would be tokenmod /shrug.&nbsp;&nbsp;
Andrew R. said: It should be doable, yes. Making it configurable for any Status Marker Library and default to the usual Status Markers would be ideal. The way ScriptCards can save and restore configuration might be a good example.&nbsp; ohhh ... like ez doable or hard work doable? &lt;wink , wink&gt;
1645507657

Edited 1645507851
DM Eddie said: Tokenmod works with custom statusmarkers.&nbsp; Also, I dunno what it conflicts with, and I don't even know if this is a serious thing to care about, but whenever I installed statusinfo in my sessions it conflicted with other scripts, no idea which ones cause the error is not informative.&nbsp; My guess would be tokenmod /shrug.&nbsp;&nbsp; I have token mod , but it does not display info like this script, at least that I am aware of.&nbsp; I do have over 80 custom token markers, and would really like much that for every single one a small chat info box would pop up in chat, due to the way my players play here. Since most info is in english, I would love to fill custom status texts in spanish for them to read and understand the current conditions affecting them
I don't know how to play your specific game but in dnd, I have a DM screen for all the status things.&nbsp;&nbsp;
Try this modification that I found on the community, I bought from the marketplace a different set of status markers and I use them with this version of statusinfo api /* * Version: 0.3.10 Made By Robin Kuiper * Modified Version: 0.3.11 Modified By Paul Derrien * * COMMANDS (with default command): * !condition [CONDITION] - Shows condition. * !condtion help - Shows help menu. * !condition config - Shows config menu. * * !condition add [condtion(s)] - Add condition(s) to selected tokens, eg. !condition add prone paralyzed * !condition remove [condtion(s)] - Remove condition(s) from selected tokens, eg. !condition remove prone paralyzed * !condition toggle [condtion(s)] - Toggles condition(s) of selected tokens, eg. !condition toggle prone paralyzed * * !condition config export - Exports the config (with conditions). * !condition config import [json] - Import the given config (with conditions). */ var StatusInfo = StatusInfo || (function() { 'use strict'; let whisper, handled = [], observers = { tokenChange: [] }; const version = "0.3.8", // Styling for the chat responses. style = "overflow: hidden; background-color: #fff; border: 1px solid #000; padding: 5px; border-radius: 5px;", buttonStyle = "background-color: #000; border: 1px solid #292929; border-radius: 3px; padding: 5px; color: #fff; text-align: center; float: right;", conditionStyle = "background-color: #fff; border: 1px solid #000; padding: 5px; border-radius: 5px;", conditionButtonStyle = "text-decoration: underline; background-color: #fff; color: #000; padding: 0", listStyle = 'list-style: none; padding: 0; margin: 0;', shaped_conditions = ['blinded', 'charmed', 'deafened', 'frightened', 'grappled', 'incapacitated', 'invisible', 'paralyzed', 'petrified', 'poisoned', 'prone', 'restrained', 'stunned', 'unconscious'], icon_images = [{id:-1,name:'blue',url:"#1076C9"}, {id:-2,name:'brown',url:"#C97310"}, {id:-3,name:'green',url:"#2FC910"}, {id:-4,name:'pink',url:"#EB75E1"}, {id:-5,name:'purple',url:"9510C9"}, {id:-6,name:'red',url:"#C91010"}, {id:-7,name:'yellow',url:"E5EB75"}, {id:0,name:'dead',url:"X"}], markers = ['blue','brown','green','pink','purple','red','yellow','dead'], script_name = 'StatusInfo', state_name = 'STATUSINFO', //New function to get all the token marker sets data getCampaignMarkers = () =&gt; { const campaignMarkers = JSON.parse(Campaign().get("token_markers")); for(var i in campaignMarkers) { icon_images.push({id:campaignMarkers[i].id, name: campaignMarkers[i].tag.toLowerCase(), url: campaignMarkers[i].url}); markers.push(campaignMarkers[i].tag.toLowerCase()); } }, handleInput = (msg) =&gt; { if (msg.type != 'api') return; // !condition BlindedBlinded // Split the message into command and argument(s) let args = msg.content.split(' '); let command = args.shift().substring(1); let extracommand = args.shift(); if(command === state[state_name].config.command){ switch(extracommand){ case 'reset': if(!playerIsGM(msg.playerid)) return; state[state_name] = {}; setDefaults(true); sendConfigMenu(); break; case 'help': if(!playerIsGM(msg.playerid)) return; sendHelpMenu(); break; case 'config': if(!playerIsGM(msg.playerid)) return; if(args.length &gt; 0){ if(args[0] === 'export' || args[0] === 'import'){ if(args[0] === 'export'){ makeAndSendMenu('&lt;pre&gt;'+HE(JSON.stringify(state[state_name]))+'&lt;/pre&gt;&lt;p&gt;Copy the entire content above and save it on your pc.&lt;/p&gt;'); } if(args[0] === 'import'){ let json; let config = msg.content.substring(('!'+state[state_name].config.command+' config import ').length); try{ json = JSON.parse(config); } catch(e) { makeAndSendMenu('This is not a valid JSON string.'); return; } state[state_name] = json; sendConfigMenu(); } return; } let setting = args.shift().split('|'); let key = setting.shift(); let value = (setting[0] === 'true') ? true : (setting[0] === 'false') ? false : setting[0]; if(key === 'prefix' &amp;&amp; value.charAt(0) !== '_'){ value = '_' + value} state[state_name].config[key] = value; whisper = (state[state_name].config.sendOnlyToGM) ? '/w gm ' : ''; } sendConfigMenu(); break; // !s config-conditions // !s config-conditions add // !s config-conditions prone // !s config-conditions prone name|blaat case 'config-conditions': if(!playerIsGM(msg.playerid)) return; let condition = args.shift(); if(condition === 'add'){ condition = args.shift(); if(!condition){ sendConditionsConfigMenu('You didn\'t give a condition name, eg. &lt;i&gt;!'+state[state_name].config.command+' config-conditions add Prone&lt;/i&gt;.'); return; } if(state[state_name].conditions[condition.toLowerCase()]){ sendConditionsConfigMenu('The condition `'+condition+'` already exists.'); return; } state[state_name].conditions[condition.toLowerCase()] = { name: condition, icon: 'red', description: '' } sendSingleConditionConfigMenu(condition.toLowerCase()); return; } if(condition === 'remove'){ let condition = args.shift(), justDoIt = (args.shift() === 'yes'); if(!justDoIt) return; if(!condition){ sendConditionsConfigMenu('You didn\'t give a condition name, eg. &lt;i&gt;!'+state[state_name].config.command+' config-conditions remove Prone&lt;/i&gt;.'); return; } if(!state[state_name].conditions[condition.toLowerCase()]){ sendConditionsConfigMenu('The condition `'+condition+'` does\'t exist.'); return; } delete state[state_name].conditions[condition.toLowerCase()]; sendConditionsConfigMenu('The condition `'+condition+'` is removed.'); } if(state[state_name].conditions[condition]){ if(args.length &gt; 0){ let setting = args.shift().split('|'); let key = setting.shift(); let value = (setting[0] === 'true') ? true : (setting[0] === 'false') ? false : setting[0]; if(key === 'name' &amp;&amp; value !== state[state_name].conditions[condition].name){ state[state_name].conditions[value.toLowerCase()] = state[state_name].conditions[condition]; delete state[state_name].conditions[condition]; condition = value.toLowerCase(); } // If we are editting the description, join the args all together in a string. value = (key === 'description') ? value + ' ' + args.join(' ') : value; state[state_name].conditions[condition][key] = value; } sendSingleConditionConfigMenu(condition); return; } sendConditionsConfigMenu(); break; case 'add': case 'remove': case 'toggle': if(!state[state_name].config.userToggle &amp;&amp; !playerIsGM(msg.playerid)) return; if(!msg.selected || !msg.selected.length){ makeAndSendMenu('No tokens are selected.'); return; } if(!args.length){ makeAndSendMenu('No condition(s) were given. Use: &lt;i&gt;!'+state[state_name].config.command+' '+extracommand+' prone&lt;/i&gt;'); return; } let tokens = msg.selected.map(s =&gt; getObj(s._type, s._id)) handleConditions(args, tokens, extracommand); break; default: if(!state[state_name].config.userAllowed &amp;&amp; !playerIsGM(msg.playerid)) return; let condition_name = extracommand; if(condition_name){ let condition; // Check if hte condition exists in the condition object. if(condition = getConditionByName(condition_name)){ // Send it to chat. sendConditionToChat(condition); }else{ sendChat((whisper) ? script_name : '', whisper + 'Condition ' + condition_name + ' does not exist.', null, {noarchive:true}); } }else{ if(!playerIsGM(msg.playerid)) return; sendMenu(msg.selected); } break; } } }, handleConditions = (conditions, tokens, type='add', error=true) =&gt; { conditions.forEach(condition_key =&gt; { if(!state[state_name].conditions[condition_key.toLowerCase()]){ if(error) makeAndSendMenu('The condition `'+condition_key+'` does not exist.'); return; } condition_key = condition_key.toLowerCase(); tokens.forEach(token =&gt; { let prevSM = token.get('statusmarkers'); let add = (type === 'add') ? true : (type === 'toggle') ? !token.get('status_'+getConditionByName(condition_key).icon) : false; token.set('status_'+getConditionByName(condition_key).icon, add); let prev = token; prev.attributes.statusmarkers = prevSM; notifyObservers('tokenChange', token, prev); if(add &amp;&amp; !handled.includes(condition_key)){ sendConditionToChat(getConditionByName(condition_key)); doHandled(condition_key); } //This generates errors on 5e OGL sheet //handleShapedSheet(token.get('represents'), condition_key, add); }); }); }, handleShapedSheet = (characterid, condition, add) =&gt; { let character = getObj('character', characterid); if(character){ let sheet = getAttrByName(character.get('id'), 'character_sheet', 'current'); if(!sheet || !sheet.toLowerCase().includes('shaped')) return; if(!shaped_conditions.includes(condition)) return; let attributes = {}; attributes[condition] = (add) ? '1': '0'; setAttrs(character.get('id'), attributes); } }, esRE = function (s) { var escapeForRegexp = /(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$)/g; return s.replace(escapeForRegexp,"\\$1"); }, HE = (function(){ var entities={ //' ' : '&amp;'+'nbsp'+';', '&lt;' : '&amp;'+'lt'+';', '&gt;' : '&amp;'+'gt'+';', "'" : '&amp;'+'#39'+';', '@' : '&amp;'+'#64'+';', '{' : '&amp;'+'#123'+';', '|' : '&amp;'+'#124'+';', '}' : '&amp;'+'#125'+';', '[' : '&amp;'+'#91'+';', ']' : '&amp;'+'#93'+';', '"' : '&amp;'+'quot'+';' }, re=new RegExp('('+_.map(_.keys(entities),esRE).join('|')+')','g'); return function(s){ return s.replace(re, function(c){ return entities[c] || c; }); }; }()), handleStatusmarkerChange = (obj, prev) =&gt; { if(handled.includes(obj.get('represents')) || !prev || !obj) return prev.statusmarkers = (typeof prev.get === 'function') ? prev.get('statusmarkers') : prev.statusmarkers; if(state[state_name].config.showDescOnStatusChange &amp;&amp; typeof prev.statusmarkers === 'string'){ // Check if the statusmarkers string is different from the previous statusmarkers string. if(obj.get('statusmarkers') !== prev.statusmarkers){ // Create arrays from the statusmarkers strings. var prevstatusmarkers = prev.statusmarkers.split(","); var statusmarkers = obj.get('statusmarkers').split(","); // Loop through the statusmarkers array. statusmarkers.forEach(function(marker){ let condition = getConditionByMarker(marker); if(!condition) return; // If it is a new statusmarkers, get the condition from the conditions object, and send it to chat. if(marker !== "" &amp;&amp; !prevstatusmarkers.includes(marker)){ if(handled.includes(condition.name.toLowerCase())) return; //sendConditionToChat(condition); handleConditions([condition.name], [obj], 'add', false) doHandled(obj.get('represents')); } }); prevstatusmarkers.forEach((marker) =&gt; { let condition = getConditionByMarker(marker); if(!condition) return; if(marker !== '' &amp;&amp; !statusmarkers.includes(marker)){ handleConditions([condition.name], [obj], 'remove', false); } }) } } }, handleAttributeChange = (obj, prev) =&gt; { if(!shaped_conditions.includes(obj.get('name'))) return; let tokens = findObjs({ represents: obj.get('characterid') }); handleConditions([obj.get('name')], tokens, (obj.get('current') === '1') ? 'add' : 'remove') }, doHandled = (what) =&gt; { handled.push(what); setTimeout(() =&gt; { handled.splice(handled.indexOf(what), 1); }, 1000); }, getConditionByMarker = (marker) =&gt; { return getObjects(state[state_name].conditions, 'icon', marker).shift() || false; }, getConditionByName = (name) =&gt; { return state[state_name].conditions[name.toLowerCase()] || false; }, sendConditionToChat = (condition, w) =&gt; { if(!condition.description || condition.description === '') return; let icon = (state[state_name].config.showIconInDescription) ? getIcon(condition.icon, 'margin-right: 5px; margin-top: 5px; display: inline-block;') || '' : ''; makeAndSendMenu(condition.description, icon+condition.name, { title_tag: 'h2', whisper: (state[state_name].config.sendOnlyToGM) ? 'gm' : '' }); }, //reconfigured getIcon() to work with data from getCampaignMarkers() getIcon = (icon, style='') =&gt; { let X = ''; let iconStyle = '' let ident = 0; let link = 'undefined'; for(var i in icon_images){ if(icon_images[i].name == icon) { ident = icon_images[i].id; link = icon_images[i].url; } } if(link === 'undefined') return false; iconStyle += 'width: 24px; height: 24px;'; if(ident &gt; 0) { iconStyle += 'background-image: url(' + link + ');' iconStyle += 'background-repeat: no-repeat;' iconStyle += 'background-position: 0px 0;' iconStyle += 'background-size: 24px 24px;' }else if(ident === 0){ iconStyle += 'color: red; margin-right: 0px;'; X = 'X'; }else{ iconStyle += 'background-color: ' + link + ';'; iconStyle += 'border: 1px solid white; border-radius: 50%;' } iconStyle += style; return '&lt;div style="'+iconStyle+'"&gt;'+X+'&lt;/div&gt;'; }, ucFirst = (string) =&gt; { return string.charAt(0).toUpperCase() + string.slice(1); }, //return an array of objects according to key, value, or key and value matching getObjects = (obj, key, val) =&gt; { var objects = []; for (var i in obj) { if (!obj.hasOwnProperty(i)) continue; if (typeof obj[i] == 'object') { objects = objects.concat(getObjects(obj[i], key, val)); } else //if key matches and value matches or if key matches and value is not passed (eliminating the case where key matches but passed value does not) if (i == key &amp;&amp; obj[i] == val || i == key &amp;&amp; val == '') { // objects.push(obj); } else if (obj[i] == val &amp;&amp; key == ''){ //only add if the object is not already in the array if (objects.lastIndexOf(obj) == -1){ objects.push(obj); } } } return objects; }, sendConditionsConfigMenu = (message) =&gt; { if(!state[state_name].conditions || typeof state[state_name].conditions === 'object') setDefaults(); let listItems = [], icons = [], check = true; for(let key in state[state_name].conditions){ let configButton = makeButton('Change', '!' + state[state_name].config.command + ' config-conditions '+key, buttonStyle); listItems.push('&lt;span style="float: left;"&gt;'+getIcon(state[state_name].conditions[key].icon, 'display: inline-block;')+state[state_name].conditions[key].name+'&lt;/span&gt; ' + configButton); if(check &amp;&amp; icons.includes(state[state_name].conditions[key].icon)){ message = message || '' + '&lt;br&gt;Multiple conditions use the same icon'; check = false; } icons.push(state[state_name].conditions[key].icon); } let backButton = makeButton('Back', '!' + state[state_name].config.command + ' config', buttonStyle + ' width: 100%'); let addButton = makeButton('Add Condition', '!' + state[state_name].config.command + ' config-conditions add ?{Name}', buttonStyle + 'float: none;'); message = (message) ? '&lt;p style="color: red"&gt;'+message+'&lt;/p&gt;' : ''; let contents = makeList(listItems, listStyle + ' overflow:hidden;', 'overflow: hidden')+'&lt;hr&gt;'+message+addButton+'&lt;hr&gt;'+backButton; makeAndSendMenu(contents, 'Conditions'); }, sendSingleConditionConfigMenu = (conditionKey, message) =&gt; { if(!conditionKey || !state[state_name].conditions[conditionKey]){ sendConditionsConfigMenu('Condition '+conditionKey+' does not exist.'); return; } let condition = state[state_name].conditions[conditionKey]; let listItems = []; let nameButton = makeButton(condition.name, '!' + state[state_name].config.command + ' config-conditions '+conditionKey+' name|?{Name}', buttonStyle); listItems.push('&lt;span style="float: left"&gt;Name: &lt;/span&gt; ' + nameButton); let markerDropdown = '?{Marker'; markers.forEach((marker) =&gt; { markerDropdown += '|'+ucFirst(marker).replace(/-/g, ' ')+','+marker }) markerDropdown += '}'; let markerButton = makeButton(getIcon(condition.icon) || condition.icon, '!' + state[state_name].config.command + ' config-conditions '+conditionKey+' icon|'+markerDropdown, buttonStyle); listItems.push('&lt;span style="float: left"&gt;Statusmarker: &lt;/span&gt; ' + markerButton); let backButton = makeButton('Back', '!' + state[state_name].config.command + ' config-conditions', buttonStyle + ' width: 100%'); let removeButton = makeButton('Remove', '!' + state[state_name].config.command + ' config-conditions remove '+conditionKey+' ?{Are you sure?|Yes,yes|No,no}', buttonStyle + ' width: 100%'); let changeButton = makeButton('Edit Description', '!' + state[state_name].config.command + ' config-conditions '+conditionKey+' description|?{Description|'+condition.description+'}', buttonStyle); message = (message) ? '&lt;p style="color: red"&gt;'+message+'&lt;/p&gt;' : ''; let contents = message+makeList(listItems, listStyle + ' overflow:hidden;', 'overflow: hidden')+'&lt;hr&gt;&lt;b&gt;Description:&lt;/b&gt;'+condition.description+changeButton+'&lt;hr&gt;&lt;p&gt;'+removeButton+backButton+'&lt;/p&gt;'; makeAndSendMenu(contents, condition.name + ' - Config'); }, sendMenu = (selected, show_names) =&gt; { let contents = ''; if(selected &amp;&amp; selected.length){ selected.forEach(s =&gt; { let token = getObj(s._type, s._id); if(token &amp;&amp; token.get('statusmarkers') !== ''){ let statusmarkers = token.get('statusmarkers').split(','); let active_conditions = []; statusmarkers.forEach(marker =&gt; { let con; if(con = getObjects(state[state_name].conditions, 'icon', marker)){ if(con[0] &amp;&amp; con[0].name) active_conditions.push(con[0].name); } }); if(active_conditions.length){ contents += '&lt;b&gt;'+token.get('name') + '\'s Conditions:&lt;/b&gt;&lt;br&gt;&lt;i&gt;' + active_conditions.join(', ') + '&lt;/i&gt;&lt;hr&gt;'; } } }); } contents += 'Toggle Condition on Selected Token(s):&lt;br&gt;' for(let condition_key in state[state_name].conditions){ let condition = state[state_name].conditions[condition_key]; contents += makeButton(getIcon(condition.icon) || condition.name, '!' + state[state_name].config.command + ' toggle '+condition_key, buttonStyle + 'float: none; margin-right: 5px;', condition.name); } makeAndSendMenu(contents, script_name + ' Menu'); }, sendConfigMenu = (first) =&gt; { let commandButton = makeButton('!'+state[state_name].config.command, '!' + state[state_name].config.command + ' config command|?{Command (without !)}', buttonStyle); let userAllowedButton = makeButton(state[state_name].config.userAllowed, '!' + state[state_name].config.command + ' config userAllowed|'+!state[state_name].config.userAllowed, buttonStyle); let userToggleButton = makeButton(state[state_name].config.userToggle, '!' + state[state_name].config.command + ' config userToggle|'+!state[state_name].config.userToggle, buttonStyle); let toGMButton = makeButton(state[state_name].config.sendOnlyToGM, '!' + state[state_name].config.command + ' config sendOnlyToGM|'+!state[state_name].config.sendOnlyToGM, buttonStyle); let statusChangeButton = makeButton(state[state_name].config.showDescOnStatusChange, '!' + state[state_name].config.command + ' config showDescOnStatusChange|'+!state[state_name].config.showDescOnStatusChange, buttonStyle); let showIconButton = makeButton(state[state_name].config.showIconInDescription, '!' + state[state_name].config.command + ' config showIconInDescription|'+!state[state_name].config.showIconInDescription, buttonStyle); let listItems = [ '&lt;span style="float: left"&gt;Command:&lt;/span&gt; ' + commandButton, '&lt;span style="float: left"&gt;Only to GM:&lt;/span&gt; '+toGMButton, '&lt;span style="float: left"&gt;Player Show:&lt;/span&gt; '+userAllowedButton, '&lt;span style="float: left"&gt;Player Toggle:&lt;/span&gt; '+userToggleButton, '&lt;span style="float: left"&gt;Show on Status Change:&lt;/span&gt; '+statusChangeButton, '&lt;span style="float: left"&gt;Display icon in chat:&lt;/span&gt; '+showIconButton ]; let configConditionsButton = makeButton('Conditions Config', '!' + state[state_name].config.command + ' config-conditions', buttonStyle + ' width: 100%'); let resetButton = makeButton('Reset Config', '!' + state[state_name].config.command + ' reset', buttonStyle + ' width: 100%'); let exportButton = makeButton('Export Config', '!' + state[state_name].config.command + ' config export', buttonStyle + ' width: 100%'); let importButton = makeButton('Import Config', '!' + state[state_name].config.command + ' config import ?{Config}', buttonStyle + ' width: 100%'); let title_text = (first) ? script_name+' First Time Setup' : script_name+' Config'; let contents = makeList(listItems, listStyle + ' overflow:hidden;', 'overflow: hidden')+'&lt;hr&gt;'+configConditionsButton+'&lt;hr&gt;&lt;p style="font-size: 80%"&gt;You can always come back to this config by typing `!'+state[state_name].config.command+' config`.&lt;/p&gt;&lt;hr&gt;'+exportButton+importButton+resetButton; makeAndSendMenu(contents, title_text) }, sendHelpMenu = (first) =&gt; { let configButton = makeButton('Config', '!' + state[state_name].config.command + ' config', buttonStyle + ' width: 100%;') let listItems = [ '&lt;span style="text-decoration: underline"&gt;!'+state[state_name].config.command+' help&lt;/span&gt; - Shows this menu.', '&lt;span style="text-decoration: underline"&gt;!'+state[state_name].config.command+' config&lt;/span&gt; - Shows the configuration menu.', '&lt;span style="text-decoration: underline"&gt;!'+state[state_name].config.command+' [CONDITION]&lt;/span&gt; - Shows the description of the condition entered.', '&amp;nbsp;', '&lt;span style="text-decoration: underline"&gt;!'+state[state_name].config.command+' add [CONDITIONS]&lt;/span&gt; - Add the given condition(s) to the selected token(s).', '&lt;span style="text-decoration: underline"&gt;!'+state[state_name].config.command+' remove [CONDITIONS]&lt;/span&gt; - Remove the given condition(s) from the selected token(s).', '&amp;nbsp;', '&lt;span style="text-decoration: underline"&gt;!'+state[state_name].config.command+' config export&lt;/span&gt; - Exports the config (with conditions).', '&lt;span style="text-decoration: underline"&gt;!'+state[state_name].config.command+' config import [JSON]&lt;/span&gt; - Imports the given config (with conditions).' ] let contents = '&lt;b&gt;Commands:&lt;/b&gt;'+makeList(listItems, listStyle)+'&lt;hr&gt;'+configButton; makeAndSendMenu(contents, script_name+' Help') }, makeAndSendMenu = (contents, title, settings) =&gt; { settings = settings || {}; settings.whisper = (typeof settings.whisper === 'undefined' || settings.whisper === 'gm') ? '/w gm ' : ''; title = (title &amp;&amp; title != '') ? makeTitle(title, settings.title_tag || '') : ''; sendChat(script_name, settings.whisper + '&lt;div style="'+style+'"&gt;'+title+contents+'&lt;/div&gt;', null, {noarchive:true}); }, makeTitle = (title, title_tag) =&gt; { title_tag = (title_tag &amp;&amp; title_tag !== '') ? title_tag : 'h3'; return '&lt;'+title_tag+' style="margin-bottom: 10px;"&gt;'+title+'&lt;/'+title_tag+'&gt;'; }, makeButton = (title, href, style, alt) =&gt; { return '&lt;a style="'+style+'" href="'+href+'" title="'+alt+'"&gt;'+title+'&lt;/a&gt;'; }, makeList = (items, listStyle, itemStyle) =&gt; { let list = '&lt;ul style="'+listStyle+'"&gt;'; items.forEach((item) =&gt; { list += '&lt;li style="'+itemStyle+'"&gt;'+item+'&lt;/li&gt;'; }); list += '&lt;/ul&gt;'; return list; }, getConditions = () =&gt; { return state[state_name].conditions; }, checkInstall = () =&gt; { if(!_.has(state, state_name)){ state[state_name] = state[state_name] || {}; } setDefaults(); getCampaignMarkers(); log(script_name + ' Ready! Command: !'+state[state_name].config.command); }, observeTokenChange = function(handler){ if(handler &amp;&amp; _.isFunction(handler)){ observers.tokenChange.push(handler); } }, notifyObservers = function(event,obj,prev){ _.each(observers[event],function(handler){ handler(obj,prev); }); }, registerEventHandlers = () =&gt; { on('chat:message', handleInput); on('change:graphic:statusmarkers', handleStatusmarkerChange); on('change:attribute', handleAttributeChange); // Handle condition descriptions when tokenmod changes the statusmarkers on a token. if('undefined' !== typeof TokenMod &amp;&amp; TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange((obj,prev) =&gt; { handleStatusmarkerChange(obj,prev); }); } if('undefined' !== typeof DeathTracker &amp;&amp; DeathTracker.ObserveTokenChange){ DeathTracker.ObserveTokenChange((obj,prev) =&gt; { handleStatusmarkerChange(obj,prev); }); } if('undefined' !== typeof InspirationTracker &amp;&amp; InspirationTracker.ObserveTokenChange){ InspirationTracker.ObserveTokenChange((obj,prev) =&gt; { handleStatusmarkerChange(obj,prev); }); } if('undefined' !== typeof CombatTracker &amp;&amp; CombatTracker.ObserveTokenChange){ CombatTracker.ObserveTokenChange((obj,prev) =&gt; { handleStatusmarkerChange(obj,prev); }); } }, setDefaults = (reset) =&gt; { const defaults = { config: { command: 'condition', userAllowed: false, userToggle: false, sendOnlyToGM: false, showDescOnStatusChange: true, showIconInDescription: true }, conditions: { blinded: { name: 'Blinded', description: '&lt;p&gt;A blinded creature can’t see and automatically fails any ability check that requires sight.&lt;/p&gt; &lt;p&gt;Attack rolls against the creature have advantage, and the creature’s Attack rolls have disadvantage.&lt;/p&gt;', icon: 'bleeding-eye' }, charmed: { name: 'Charmed', description: '&lt;p&gt;A charmed creature can’t Attack the charmer or target the charmer with harmful Abilities or magical effects.&lt;/p&gt; &lt;p&gt;The charmer has advantage on any ability check to interact socially with the creature.&lt;/p&gt;', icon: 'broken-heart' }, deafened: { name: 'Deafened', description: '&lt;p&gt;A deafened creature can’t hear and automatically fails any ability check that requires hearing.&lt;/p&gt;', icon: 'edge-crack' }, frightened: { name: 'Frightened', description: '&lt;p&gt;A frightened creature has disadvantage on Ability Checks and Attack rolls while the source of its fear is within line of sight.&lt;/p&gt; &lt;p&gt;The creature can’t willingly move closer to the source of its fear.&lt;/p&gt;', icon: 'screaming' }, grappled: { name: 'Grappled', description: '&lt;p&gt;A grappled creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.&lt;/p&gt; &lt;p&gt;The condition ends if the Grappler is &lt;i&gt;incapacitated&lt;/i&gt;.&lt;/p&gt; &lt;p&gt;The condition also ends if an effect removes the grappled creature from the reach of the Grappler or Grappling effect, such as when a creature is hurled away by the Thunderwave spell.&lt;/p&gt;', icon: 'grab' }, incapacitated: { name: 'Incapacitated', description: '&lt;p&gt;An incapacitated creature can’t take actions or reactions.&lt;/p&gt;', icon: 'interdiction' }, inspiration: { name: 'Inspiration', description: '&lt;p&gt;If you have inspiration, you can expend it when you make an Attack roll, saving throw, or ability check. Spending your inspiration gives you advantage on that roll.&lt;/p&gt; &lt;p&gt;Additionally, if you have inspiration, you can reward another player for good roleplaying, clever thinking, or simply doing something exciting in the game. When another player character does something that really contributes to the story in a fun and interesting way, you can give up your inspiration to give that character inspiration.&lt;/p&gt;', icon: 'black-flag' }, invisibility: { name: 'Invisibility', description: '&lt;p&gt;An invisible creature is impossible to see without the aid of magic or a Special sense. For the purpose of Hiding, the creature is heavily obscured. The creature’s location can be detected by any noise it makes or any tracks it leaves.&lt;/p&gt; &lt;p&gt;Attack rolls against the creature have disadvantage, and the creature’s Attack rolls have advantage.&lt;/p&gt;', icon: 'ninja-mask' }, paralyzed: { name: 'Paralyzed', description: '&lt;p&gt;A paralyzed creature is &lt;i&gt;incapacitated&lt;/i&gt; and can’t move or speak.&lt;/p&gt; &lt;p&gt;The creature automatically fails Strength and Dexterity saving throws.&lt;/p&gt; &lt;p&gt;Attack rolls against the creature have advantage.&lt;/p&gt; &lt;p&gt;Any Attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.&lt;/p&gt;', icon: 'pummeled' }, petrified: { name: 'Petrified', description: '&lt;p&gt;A petrified creature is transformed, along with any nonmagical object it is wearing or carrying, into a solid inanimate substance (usually stone). Its weight increases by a factor of ten, and it ceases aging.&lt;/p&gt; &lt;p&gt;The creature is &lt;i&gt;incapacitated&lt;/i&gt;, can’t move or speak, and is unaware of its surroundings.&lt;/p&gt; &lt;p&gt;Attack rolls against the creature have advantage.&lt;/p&gt; &lt;p&gt;The creature automatically fails Strength and Dexterity saving throws.&lt;/p&gt; &lt;p&gt;The creature has Resistance to all damage.&lt;/p&gt; &lt;p&gt;The creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.&lt;/p&gt;', icon: 'frozen-orb' }, poisoned: { name: 'Poisoned', description: '&lt;p&gt;A poisoned creature has disadvantage on Attack rolls and Ability Checks.&lt;/p&gt;', icon: 'chemical-bolt' }, prone: { name: 'Prone', description: '&lt;p&gt;A prone creature’s only Movement option is to crawl, unless it stands up and thereby ends the condition.&lt;/p&gt; &lt;p&gt;The creature has disadvantage on Attack rolls.&lt;/p&gt; &lt;p&gt;An Attack roll against the creature has advantage if the attacker is within 5 feet of the creature. Otherwise, the Attack roll has disadvantage.&lt;/p&gt;', icon: 'back-pain' }, restrained: { name: 'Restrained', description: '&lt;p&gt;A restrained creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.&lt;/p&gt; &lt;p&gt;Attack rolls against the creature have advantage, and the creature’s Attack rolls have disadvantage.&lt;/p&gt; &lt;p&gt;The creature has disadvantage on Dexterity saving throws.&lt;/p&gt;', icon: 'fishing-net' }, stunned: { name: 'Stunned', description: '&lt;p&gt;A stunned creature is &lt;i&gt;incapacitated&lt;/i&gt;, can’t move, and can speak only falteringly.&lt;/p&gt; &lt;p&gt;The creature automatically fails Strength and Dexterity saving throws.&lt;/p&gt; &lt;p&gt;Attack rolls against the creature have advantage.&lt;/p&gt;', icon: 'fist' }, unconscious: { name: 'Unconscious', description: '&lt;p&gt;An unconscious creature is &lt;i&gt;incapacitated&lt;/i&gt;, can’t move or speak, and is unaware of its surroundings.&lt;/p&gt; &lt;p&gt;The creature drops whatever it’s holding and falls prone.&lt;/p&gt; &lt;p&gt;The creature automatically fails Strength and Dexterity saving throws.&lt;/p&gt; &lt;p&gt;Attack rolls against the creature have advantage.&lt;/p&gt; &lt;p&gt;Any Attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.&lt;/p&gt;', icon: 'sleepy' }, }, }; if(!state[state_name].config){ state[state_name].config = defaults.config; }else{ if(!state[state_name].config.hasOwnProperty('command')){ state[state_name].config.command = defaults.config.command; } if(!state[state_name].config.hasOwnProperty('userAllowed')){ state[state_name].config.userAllowed = defaults.config.userAllowed; } if(!state[state_name].config.hasOwnProperty('userToggle')){ state[state_name].config.userToggle = defaults.config.userToggle; } if(!state[state_name].config.hasOwnProperty('sendOnlyToGM')){ state[state_name].config.sendOnlyToGM = defaults.config.sendOnlyToGM; } if(!state[state_name].config.hasOwnProperty('showDescOnStatusChange')){ state[state_name].config.showDescOnStatusChange = defaults.config.showDescOnStatusChange; } if(!state[state_name].config.hasOwnProperty('showIconInDescription')){ state[state_name].config.showIconInDescription = defaults.config.showIconInDescription; } } if(!state[state_name].conditions || typeof state[state_name].conditions !== 'object'){ state[state_name].conditions = defaults.conditions; } whisper = (state[state_name].config.sendOnlyToGM) ? '/w gm ' : ''; if(!state[state_name].config.hasOwnProperty('firsttime') &amp;&amp; !reset){ sendConfigMenu(true); state[state_name].config.firsttime = false; } }; return { checkInstall, ObserveTokenChange: observeTokenChange, registerEventHandlers, getConditions, getConditionByName, handleConditions, sendConditionToChat, getIcon, version }; })(); on('ready', () =&gt; { 'use strict'; StatusInfo.checkInstall(); StatusInfo.registerEventHandlers(); });
OH MY PANTHEON OF GODS!!!!! You ... you DIT IT! THIS IS EXACTLY WHAT I NEEDED FOR THE LOVE OF GODDESS OF SCRIPTS!!! Thank you thank youuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu I can finally show my players conditions in spanish when I tag a token WOWOWOWOWOWOWOWOWOWOWOW! Thank you thank youuuu :D
1645574961
Andrew R.
Pro
Sheet Author
Nice one, Warlord! &lt;applause&gt;