diff --git a/CharSheetUtils/1.1/index.js b/CharSheetUtils/1.1/index.js new file mode 100644 index 0000000000..61090a4742 --- /dev/null +++ b/CharSheetUtils/1.1/index.js @@ -0,0 +1,163 @@ +/** + * Create the CharSheetUtils library. All the functionality of this library + * is exposed through its static methods. + */ +var CharSheetUtils = (() => { + 'use strict'; + + return class { + /** + * Asynchronously gets the value of a character sheet attribute. + * @param {Character} character + * @param {string} attr + * @return {Promise} + * Contains the value of the attribute. + */ + static async getSheetAttr(character, attr) { + if(attr.includes('/')) + return CharSheetUtils.getSheetRepeatingAttr(character, attr); + else { + return getSheetItem(character.id, attr).then((value) => { + return value; + }); + } + } + + /** + * Asynchronously gets the value of a character sheet attribute from a + * repeating row. + * @param {Character} character + * @param {string} attr + * Here, attr has the format "sectionName/nameFieldName/nameFieldValue/valueFieldName". + * For example: "skills/name/perception/total" + * @return {Promise} + * Contains the value of the attribute. + */ + static getSheetRepeatingAttr(character, attr) { + let parts = attr.split('/'); + if(parts.length < 4) return; + + let sectionName = parts[0]; + let nameFieldName = parts[1]; + let nameFieldValue = parts[2].toLowerCase(); + let valueFieldName = parts[3]; + + // Find the row with the given name. + return CharSheetUtils.getSheetRepeatingRow(character, sectionName, rowAttrs => { + let nameField = rowAttrs[nameFieldName]; + if(!nameField) + return false; + return nameField.get('current').toLowerCase().trim() === nameFieldValue; + }) + + // Get the current value of that row. + .then(rowAttrs => { + if(!rowAttrs) + return NaN; + + let valueField = rowAttrs[valueFieldName]; + if(!valueField) + return NaN; + return valueField.get('current'); + }); + } + + /** + * Gets the map of attributes inside of a repeating section row. + * @param {Character} character + * @param {string} section + * The name of the repeating section. + * @param {func} rowFilter + * A filter function to find the correct row. The argument passed to it is a + * map of attribute names (without the repeating section ID part - e.g. "name" + * instead of "repeating_skills_-123abc_name") to their actual attributes in + * the current row being filtered. The function should return true iff it is + * the correct row we're looking for. + * @return {Promise} + * Contains the map of attributes. + */ + static getSheetRepeatingRow(character, section, rowFilter) { + // Get all attributes in this section and group them by row. + let attrs = findObjs({ + _type: 'attribute', + _characterid: character.get('_id') + }); + + // Group the attributes by row. + let rows = {}; + _.each(attrs, attr => { + let regex = new RegExp(`repeating_${section}_(-([0-9a-zA-Z\-_](?!_storage))+?|\$\d+?)_([0-9a-zA-Z\-_]+)`); + let match = attr.get('name').match(regex); + if(match) { + let rowId = match[1]; + let attrName = match[3]; + if(!rows[rowId]) + rows[rowId] = {}; + + rows[rowId][attrName] = attr; + } + }); + + // Find the row that matches our filter. + return Promise.resolve(_.find(rows, rowAttrs => { + return rowFilter(rowAttrs); + })); + } + + /** + * Asynchronously rolls a dice roll expression and returns the roll result + * in a Promise. + * @param {string} expr + * @return {Promise} + */ + static rollAsync(expr) { + return new Promise((resolve, reject) => { + sendChat('CharSheetUtils', '/w gm [[' + expr + ']]', (msg) => { + try { + let results = msg[0].inlinerolls[0].results; + resolve(results); + } + catch(err) { + log(expr); + reject(err); + } + }); + }); + } + + static send(text) { + sendChat('API', '' + `
${text}
`, null, {noarchive:true}); + } + + static async handleInput(msg) { + let args = msg.content.split(' '); + let command = args.shift().substring(1); + let extracommand = args.shift(); + + if (command === 'roll') { + let t = await CharSheetUtils.rollAsync(extracommand); + CharSheetUtils.send(t); + } + + else if(command === 'getattr') { + if(!msg.selected) return; + for (const s of msg.selected) { + if (s._type !== 'graphic') continue; + + let token = getObj('graphic', s._id); + if (!token) continue; + let character = getObj('character', token.get('represents')); + + let t = await CharSheetUtils.getSheetAttr(character, extracommand); + CharSheetUtils.send(t); + } + } + } + }; +})(); + +on('ready',function() { + 'use strict'; + + on('chat:message', CharSheetUtils.handleInput); +}); \ No newline at end of file diff --git a/CharSheetUtils/script.json b/CharSheetUtils/script.json index 29075a12bd..66c6e12b06 100644 --- a/CharSheetUtils/script.json +++ b/CharSheetUtils/script.json @@ -1,12 +1,14 @@ { "name": "Character Sheet Utils", "script": "index.js", - "version": "1.0", - "previousversions": [], + "version": "1.1", + "previousversions": [ + "1.0" + ], "description": "# Character Sheet Utils\r\rThis script provides a collection of utility functions for reading and writing\rattributes from a character sheet, including support for attributes in\rrepeating sections and calculated attributes. This script does nothing on\rits own. It is a helper library meant to be used by other scripts.\r\rFull documentation for the functions provided by this script are given\rin the jsdoc annotations in the source code.\r\r## Help\r\rMy scripts are provided 'as-is', without warranty of any kind, expressed or implied.\r\rThat said, if you experience any issues while using this script,\rneed help using it, or if you have a neat suggestion for a new feature,\rplease shoot me a PM:\rhttps://app.roll20.net/users/46544/ada-l\r\rWhen messaging me about an issue, please be sure to include any error messages that\rappear in your API Console Log, any configurations you've got set up for the\rscript in the VTT, and any options you've got set up for the script on your\rgame's API Scripts page. The more information you provide me, the better the\rchances I'll be able to help.\r\r## Show Support\r\rIf you would like to show your appreciation and support for the work I do in writing,\rupdating, maintaining, and providing tech support my API scripts,\rplease consider buying one of my art packs from the Roll20 marketplace:\r\rhttps://marketplace.roll20.net/browse/publisher/165/ada-lindberg\r", "authors": "Ada Lindberg", "roll20userid": 46544, "useroptions": [], "dependencies": [], "modifies": {} -} +} \ No newline at end of file diff --git a/CharSheetUtils/src/index.js b/CharSheetUtils/src/index.js index fe92f2b5ca..61090a4742 100644 --- a/CharSheetUtils/src/index.js +++ b/CharSheetUtils/src/index.js @@ -6,30 +6,6 @@ var CharSheetUtils = (() => { 'use strict'; return class { - /** - * Attempts to force a calculated attribute to be corrected by - * setting it. - * @param {Character} character - * @param {string} attr - */ - static forceAttrCalculation(character, attr) { - // Attempt to force the calculation of the attribute by setting it. - createObj('attribute', { - _characterid: character.get('_id'), - name: attr, - current: -9999 - }); - - // Then try again. - return CharSheetUtils.getSheetAttr(character, attr) - .then(result => { - if(_.isNumber(result)) - return result; - else - log('Could not calculate attribute: ' + attr + ' - ' + result); - }); - } - /** * Asynchronously gets the value of a character sheet attribute. * @param {Character} character @@ -37,27 +13,12 @@ var CharSheetUtils = (() => { * @return {Promise} * Contains the value of the attribute. */ - static getSheetAttr(character, attr) { + static async getSheetAttr(character, attr) { if(attr.includes('/')) return CharSheetUtils.getSheetRepeatingAttr(character, attr); else { - let rollExpr = '@{' + character.get('name') + '|' + attr + '}'; - return CharSheetUtils.rollAsync(rollExpr) - .then((roll) => { - if(roll) - return roll.total; - else - throw new Error('Could not resolve roll expression: ' + rollExpr); - }) - .then(value => { - if(_.isNumber(value)) - return value; - - // If the attribute is autocalculated, but could its current value - // could not be resolved, try to force it to calculate its value as a - // last-ditch effort. - else - return CharSheetUtils.forceAttrCalculation(character, attr); + return getSheetItem(character.id, attr).then((value) => { + return value; }); } } @@ -74,6 +35,8 @@ var CharSheetUtils = (() => { */ static getSheetRepeatingAttr(character, attr) { let parts = attr.split('/'); + if(parts.length < 4) return; + let sectionName = parts[0]; let nameFieldName = parts[1]; let nameFieldValue = parts[2].toLowerCase(); @@ -161,5 +124,40 @@ var CharSheetUtils = (() => { }); }); } + + static send(text) { + sendChat('API', '' + `
${text}
`, null, {noarchive:true}); + } + + static async handleInput(msg) { + let args = msg.content.split(' '); + let command = args.shift().substring(1); + let extracommand = args.shift(); + + if (command === 'roll') { + let t = await CharSheetUtils.rollAsync(extracommand); + CharSheetUtils.send(t); + } + + else if(command === 'getattr') { + if(!msg.selected) return; + for (const s of msg.selected) { + if (s._type !== 'graphic') continue; + + let token = getObj('graphic', s._id); + if (!token) continue; + let character = getObj('character', token.get('represents')); + + let t = await CharSheetUtils.getSheetAttr(character, extracommand); + CharSheetUtils.send(t); + } + } + } }; })(); + +on('ready',function() { + 'use strict'; + + on('chat:message', CharSheetUtils.handleInput); +}); \ No newline at end of file