From 4c355d6ffcdd62a7fa0d68f0eddb8876c59d63f7 Mon Sep 17 00:00:00 2001 From: Nicole Brooks <83994164+NBrooks-Roll20@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:43:27 -0500 Subject: [PATCH] Adding 2024 support to StatusInfo script --- StatusInfo/0.3.12/StatusInfo.js | 793 ++++++++++++++++++++++++++++++++ StatusInfo/README.md | 2 +- StatusInfo/StatusInfo.js | 2 +- StatusInfo/script.json | 6 +- 4 files changed, 798 insertions(+), 5 deletions(-) create mode 100644 StatusInfo/0.3.12/StatusInfo.js diff --git a/StatusInfo/0.3.12/StatusInfo.js b/StatusInfo/0.3.12/StatusInfo.js new file mode 100644 index 0000000000..d3054aa866 --- /dev/null +++ b/StatusInfo/0.3.12/StatusInfo.js @@ -0,0 +1,793 @@ +/* + * Version: 0.3.11 + * Made By Robin Kuiper + * Skype: RobinKuiper.eu + * Discord: Atheos#1095 + * My Discord Server: https://discord.gg/AcC9VME + * Roll20: https://app.roll20.net/users/1226016/robin + * Roll20 Thread: https://app.roll20.net/forum/post/6252784/script-statusinfo + * Roll20 Wiki: https://wiki.roll20.net/Script:StatusInfo + * Github: https://github.com/RobinKuiper/Roll20APIScripts + * Reddit: https://www.reddit.com/user/robinkuiper/ + * Patreon: https://patreon.com/robinkuiper + * Paypal.me: https://www.paypal.me/robinkuiper + * + * 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). + * + * TODO: + * Icon span + * whisper system + * stylings +*/ + +var StatusInfo = StatusInfo || (function() { + 'use strict'; + + let whisper, handled = [], + observers = { + tokenChange: [] + }; + + + const version = "0.3.11", + + // 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;', + + icon_image_positions = {red:"#C91010",blue:"#1076C9",green:"#2FC910",brown:"#C97310",purple:"#9510C9",pink:"#EB75E1",yellow:"#E5EB75",dead:"X",skull:0,sleepy:34,"half-heart":68,"half-haze":102,interdiction:136,snail:170,"lightning-helix":204,spanner:238,"chained-heart":272,"chemical-bolt":306,"death-zone":340,"drink-me":374,"edge-crack":408,"ninja-mask":442,stopwatch:476,"fishing-net":510,overdrive:544,strong:578,fist:612,padlock:646,"three-leaves":680,"fluffy-wing":714,pummeled:748,tread:782,arrowed:816,aura:850,"back-pain":884,"black-flag":918,"bleeding-eye":952,"bolt-shield":986,"broken-heart":1020,cobweb:1054,"broken-shield":1088,"flying-flag":1122,radioactive:1156,trophy:1190,"broken-skull":1224,"frozen-orb":1258,"rolling-bomb":1292,"white-tower":1326,grab:1360,screaming:1394,grenade:1428,"sentry-gun":1462,"all-for-one":1496,"angel-outfit":1530,"archery-target":1564}, + markers = ['blue', 'brown', 'green', 'pink', 'purple', 'red', 'yellow', '-', 'all-for-one', 'angel-outfit', 'archery-target', 'arrowed', 'aura', 'back-pain', 'black-flag', 'bleeding-eye', 'bolt-shield', 'broken-heart', 'broken-shield', 'broken-skull', 'chained-heart', 'chemical-bolt', 'cobweb', 'dead', 'death-zone', 'drink-me', 'edge-crack', 'fishing-net', 'fist', 'fluffy-wing', 'flying-flag', 'frozen-orb', 'grab', 'grenade', 'half-haze', 'half-heart', 'interdiction', 'lightning-helix', 'ninja-mask', 'overdrive', 'padlock', 'pummeled', 'radioactive', 'rolling-bomb', 'screaming', 'sentry-gun', 'skull', 'sleepy', 'snail', 'spanner', 'stopwatch','strong', 'three-leaves', 'tread', 'trophy', 'white-tower'], + shaped_conditions = ['blinded', 'charmed', 'deafened', 'frightened', 'grappled', 'incapacitated', 'invisible', 'paralyzed', 'petrified', 'poisoned', 'prone', 'restrained', 'stunned', 'unconscious'], + + script_name = 'StatusInfo', + state_name = 'STATUSINFO', + + handleInput = (msg) => { + 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 > 0){ + if(args[0] === 'export' || args[0] === 'import'){ + if(args[0] === 'export'){ + makeAndSendMenu('
'+HE(JSON.stringify(state[state_name]))+'
Copy the entire content above and save it on your pc.
'); + } + 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' && 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. !'+state[state_name].config.command+' config-conditions add Prone.'); + 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. !'+state[state_name].config.command+' config-conditions remove Prone.'); + 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 > 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' && 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 && !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: !'+state[state_name].config.command+' '+extracommand+' prone'); + return; + } + + let tokens = msg.selected.map(s => getObj(s._type, s._id)) + handleConditions(args, tokens, extracommand); + break; + + default: + if(!state[state_name].config.userAllowed && !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) => { + conditions.forEach(condition_key => { + 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 => { + 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 && !handled.includes(condition_key)){ + sendConditionToChat(getConditionByName(condition_key)); + doHandled(condition_key); + } + + handleShapedSheet(token.get('represents'), condition_key, add); + }); + }); + }, + + handleShapedSheet = (characterid, condition, add) => { + let character = getObj('character', characterid); + if(character){ + let sheet = character.get("charactersheetname"); + 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={ + //' ' : '&'+'nbsp'+';', + '<' : '&'+'lt'+';', + '>' : '&'+'gt'+';', + "'" : '&'+'#39'+';', + '@' : '&'+'#64'+';', + '{' : '&'+'#123'+';', + '|' : '&'+'#124'+';', + '}' : '&'+'#125'+';', + '[' : '&'+'#91'+';', + ']' : '&'+'#93'+';', + '"' : '&'+'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) => { + 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 && 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 !== "" && !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) => { + let condition = getConditionByMarker(marker); + if(!condition) return; + + if(marker !== '' && !statusmarkers.includes(marker)){ + handleConditions([condition.name], [obj], 'remove', false); + } + }) + } + } + }, + + handleAttributeChange = (obj, prev) => { + 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) => { + handled.push(what); + setTimeout(() => { + handled.splice(handled.indexOf(what), 1); + }, 1000); + }, + + getConditionByMarker = (marker) => { + return getObjects(state[state_name].conditions, 'icon', marker).shift() || false; + }, + + getConditionByName = (name) => { + return state[state_name].conditions[name.toLowerCase()] || false; + }, + + sendConditionToChat = (condition, w) => { + 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' : '' + }); + }, + + getIcon = (icon, style='') => { + let X = ''; + let iconStyle = '' + + if(typeof icon_image_positions[icon] === 'undefined') return false; + //if(!icon_image_positions[icon]) return false; + + iconStyle += 'width: 24px; height: 24px;'; + + if(Number.isInteger(icon_image_positions[icon])){ + iconStyle += 'background-image: url(https://roll20.net/images/statussheet.png);' + iconStyle += 'background-repeat: no-repeat;' + iconStyle += 'background-position: -'+icon_image_positions[icon]+'px 0;' + }else if(icon_image_positions[icon] === 'X'){ + iconStyle += 'color: red; margin-right: 0px;'; + X = 'X'; + }else{ + iconStyle += 'background-color: ' + icon_image_positions[icon] + ';'; + iconStyle += 'border: 1px solid white; border-radius: 50%;' + } + + iconStyle += style; + + // TODO: Make span + return ''+message+'
' : ''; + let contents = makeList(listItems, listStyle + ' overflow:hidden;', 'overflow: hidden')+''+message+'
' : ''; + let contents = message+makeList(listItems, listStyle + ' overflow:hidden;', 'overflow: hidden')+''+removeButton+backButton+'
'; + makeAndSendMenu(contents, condition.name + ' - Config'); + }, + + sendMenu = (selected, show_names) => { + let contents = ''; + if(selected && selected.length){ + selected.forEach(s => { + let token = getObj(s._type, s._id); + if(token && token.get('statusmarkers') !== ''){ + let statusmarkers = token.get('statusmarkers').split(','); + let active_conditions = []; + statusmarkers.forEach(marker => { + let con; + if(con = getObjects(state[state_name].conditions, 'icon', marker)){ + if(con[0] && con[0].name) active_conditions.push(con[0].name); + } + }); + + if(active_conditions.length){ + contents += ''+token.get('name') + '\'s Conditions:You can always come back to this config by typing `!'+state[state_name].config.command+' config`.
A blinded creature can’t see and automatically fails any ability check that requires sight.
Attack rolls against the creature have advantage, and the creature’s Attack rolls have disadvantage.
', + icon: 'bleeding-eye' + }, + charmed: { + name: 'Charmed', + description: 'A charmed creature can’t Attack the charmer or target the charmer with harmful Abilities or magical effects.
The charmer has advantage on any ability check to interact socially with the creature.
', + icon: 'broken-heart' + }, + deafened: { + name: 'Deafened', + description: 'A deafened creature can’t hear and automatically fails any ability check that requires hearing.
', + icon: 'edge-crack' + }, + frightened: { + name: 'Frightened', + description: 'A frightened creature has disadvantage on Ability Checks and Attack rolls while the source of its fear is within line of sight.
The creature can’t willingly move closer to the source of its fear.
', + icon: 'screaming' + }, + grappled: { + name: 'Grappled', + description: 'A grappled creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.
The condition ends if the Grappler is incapacitated.
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.
', + icon: 'grab' + }, + incapacitated: { + name: 'Incapacitated', + description: 'An incapacitated creature can’t take actions or reactions.
', + icon: 'interdiction' + }, + inspiration: { + name: 'Inspiration', + description: '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.
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.
', + icon: 'black-flag' + }, + invisibility: { + name: 'Invisibility', + description: '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.
Attack rolls against the creature have disadvantage, and the creature’s Attack rolls have advantage.
', + icon: 'ninja-mask' + }, + paralyzed: { + name: 'Paralyzed', + description: 'A paralyzed creature is incapacitated and can’t move or speak.
The creature automatically fails Strength and Dexterity saving throws.
Attack rolls against the creature have advantage.
Any Attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.
', + icon: 'pummeled' + }, + petrified: { + name: 'Petrified', + description: '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.
The creature is incapacitated, can’t move or speak, and is unaware of its surroundings.
Attack rolls against the creature have advantage.
The creature automatically fails Strength and Dexterity saving throws.
The creature has Resistance to all damage.
The creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.
', + icon: 'frozen-orb' + }, + poisoned: { + name: 'Poisoned', + description: 'A poisoned creature has disadvantage on Attack rolls and Ability Checks.
', + icon: 'chemical-bolt' + }, + prone: { + name: 'Prone', + description: 'A prone creature’s only Movement option is to crawl, unless it stands up and thereby ends the condition.
The creature has disadvantage on Attack rolls.
An Attack roll against the creature has advantage if the attacker is within 5 feet of the creature. Otherwise, the Attack roll has disadvantage.
', + icon: 'back-pain' + }, + restrained: { + name: 'Restrained', + description: 'A restrained creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.
Attack rolls against the creature have advantage, and the creature’s Attack rolls have disadvantage.
The creature has disadvantage on Dexterity saving throws.
', + icon: 'fishing-net' + }, + stunned: { + name: 'Stunned', + description: 'A stunned creature is incapacitated, can’t move, and can speak only falteringly.
The creature automatically fails Strength and Dexterity saving throws.
Attack rolls against the creature have advantage.
', + icon: 'fist' + }, + unconscious: { + name: 'Unconscious', + description: 'An unconscious creature is incapacitated, can’t move or speak, and is unaware of its surroundings.
The creature drops whatever it’s holding and falls prone.
The creature automatically fails Strength and Dexterity saving throws.
Attack rolls against the creature have advantage.
Any Attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.
', + 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') && !reset){ + sendConfigMenu(true); + state[state_name].config.firsttime = false; + } + }; + + return { + checkInstall, + ObserveTokenChange: observeTokenChange, + registerEventHandlers, + getConditions, + getConditionByName, + handleConditions, + sendConditionToChat, + getIcon, + version + }; +})(); + +on('ready', () => { + 'use strict'; + + StatusInfo.checkInstall(); + StatusInfo.registerEventHandlers(); +}); diff --git a/StatusInfo/README.md b/StatusInfo/README.md index 6a36cff9b0..670ab3e04a 100644 --- a/StatusInfo/README.md +++ b/StatusInfo/README.md @@ -14,7 +14,7 @@ --- ``` -LATEST UPDATE: It now allows you to create and edit conditions, export/import the config, and add/remove/toggle condition(s) to/from token(s), see below. +LATEST UPDATE: Updated to work with the D&D 2024 sheet. ``` StatusInfo works nicely together with [Tokenmod](https://app.roll20.net/forum/post/4225825/script-update-tokenmod-an-interface-to-adjusting-properties-of-a-token-from-a-macro-or-the-chat-area/?pageforid=4225825#post-4225825) and my own [DeathTracker](https://github.com/RobinKuiper/Roll20APIScripts/tree/master/DeathTracker) and [InspirationTracker](https://github.com/RobinKuiper/Roll20APIScripts/tree/master/InspirationTracker) scripts. diff --git a/StatusInfo/StatusInfo.js b/StatusInfo/StatusInfo.js index dfcd1e31ff..d3054aa866 100644 --- a/StatusInfo/StatusInfo.js +++ b/StatusInfo/StatusInfo.js @@ -264,7 +264,7 @@ var StatusInfo = StatusInfo || (function() { handleShapedSheet = (characterid, condition, add) => { let character = getObj('character', characterid); if(character){ - let sheet = getAttrByName(character.get('id'), 'character_sheet', 'current'); + let sheet = character.get("charactersheetname"); if(!sheet || !sheet.toLowerCase().includes('shaped')) return; if(!shaped_conditions.includes(condition)) return; diff --git a/StatusInfo/script.json b/StatusInfo/script.json index 11f35ea60a..846b6f6797 100644 --- a/StatusInfo/script.json +++ b/StatusInfo/script.json @@ -1,9 +1,9 @@ { "name": "StatusInfo", "script": "StatusInfo.js", - "version": "0.3.11", - "previousversions": ["0.3.2", "0.3.4", "0.3.6", "0.3.8", "0.3.10"], - "description": "All info and latest version on \n\n https://github.com/RobinKuiper/Roll20APIScripts/tree/master/StatusInfo", + "version": "0.3.12", + "previousversions": ["0.3.2", "0.3.4", "0.3.6", "0.3.8", "0.3.10", "0.3.11"], + "description": "All info and latest version on \n\n https://github.com/RobinKuiper/Roll20APIScripts/tree/master/StatusInfo\n\nThis script is compatible with the D&D 2024 Character Sheet.", "authors": "Robin Kuiper", "roll20userid": "1226016", "patreon": "https://www.patreon.com/robinkuiper",