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 .
×
Advertisement Create a free account

How to read default attribute values

1482923404
Samuel Penn
KS Backer
Not quite sure whether this falls under the Character Sheets section or the API section, since I'm writing a script to read attributes from a character sheet, but since it's API focused, I've decided to post it here, but apologies if I chose wrong. I have a script which outputs rolls for lots of skills at once, and I was finding that it was taking 100ms+ to read each attribute, leading to a 2-3sec pause before outputting anything if I had a dozen skills. So, I've tried optimising by reading all attributes for a character in one go with findObjs(), then reading results from my local array. This seems to be a lot quicker, but is failing where an attribute relies on the default value provided by the character sheet. How do I determine the default value for an attribute, without resorting to getAttrByName()? Is there an API call to read all these in one go using something like findObjs? Thanks.
1482928764
The Aaron
Forum Champion
API Scripter
GetAttrByName() is the only way to get default values. The best suggestion I can make is only using it on the ones you don't find with findObjs(). You could use some technique like promise.all() or _.after() to parallelize all the GetAttrByName() calls. 
1482932383
Samuel Penn
KS Backer
Okay, thanks. What I've done is if there's no value available in the findObjs() results, I make a call to getAttrByName() and cache the result. Looking up a default value from the cache is a lot quicker for subsequent calls. Hopefully defaults won't change that often, so caching them between refreshes of the script/sandbox should be fine.
1482935976
Stephen L.
Pro
Marketplace Creator
Sheet Author
API Scripter
If the default value is also a calculated value, getAttrByName() might not work. When I've tried to use it for that, it just returns the expression string.
Stephen L. said: If the default value is also a calculated value, getAttrByName() might not work. When I've tried to use it for that, it just returns the expression string. This is true. I have had similar experiences with using getAttrByName(). If anyone has a function that will recursively call getAttrByName() for each value in addition to the order of operations of the expression that would be fantastic. Otherwise, I have had some success using the callback functionality of sendChat and inline calculations to get the value of the attribute.
1482938038

Edited 1482938301
Stephen L.
Pro
Marketplace Creator
Sheet Author
API Scripter
I've had some success with calculated values by doing an asynchronous inline chat roll and extracting the result in the callback, but it can get complicated, especially if the dependent values aren't set. Curiously enough, just calling a macro for a calculated default attribute in the chat (not through the API) seems to resolve the values fine. @DEVS: Could getAttrByName() be fixed to resolve calculated values as if they were called through a macro in the chat?
1482941124

Edited 1482941574
Brian
Sheet Author
API Scripter
Stephen L. said: @DEVS: Could getAttrByName() be fixed to resolve calculated values as if they were called through a macro in the chat? The API doesn't have any information on whether an attribute is autocalc or not. In fact, that's not even a property of the attribute, but of the form element on the character sheet displaying the attribute. You could conceivably have an input on your sheet that's autocalc, and another input on the same sheet with the same name (making it backed by the same attribute object) that isn't autocalc. At best, the devs could probably run the result of getAttrByName through the dice engine, but that would ultimately be no different from manually passing the result to a roll in sendChat with a callback parameter, since it would need to be asynchronous for the same reasons. You could redefine getAttrByName yourself for the same result: var originalGetAttrByName = getAttrByName; getAttrByName = function(characterId, attributeName, valueType, callback) { var value = originalGetAttrByName(characterId, attributeName, valueType); if (callback === undefined) return value; sendChat('', '/r ' + value, function(ops) { callback(JSON.parse(ops[0].content)); }); }
1482941722
Stephen L.
Pro
Marketplace Creator
Sheet Author
API Scripter
Yeah, I guess if getAttrByName() became asynchronous, that would break a lot of scripts using the current synchronous version. I've found also that if you just try to resolve an autocalced value through an inline roll in sendChat, if that calculated attribute is dependent on other calculated attributes that have not been initialized, then it will similarly just get the autocalc expression, instead of the resolved value. Sending a macro in the chat using one of these kinds of autocalced values tends to work fine though.
1482942286

Edited 1482942558
Stephen L.
Pro
Marketplace Creator
Sheet Author
API Scripter
Here's an excerpt from my It's a Trap script for what I've been doing to try to resolve autocalculated attributes: /** * Base class for trap themes: System-specific strategies for handling * automation of trap activation results and passive searching. * @abstract */ var TrapTheme = (() => { 'use strict'; return class TrapTheme { ... /** * Asynchronously gets the value of a character sheet attribute. * @param {Character} character * @param {string} attr * @return {Promise<any>} * Contains the value of the attribute. */ static getSheetAttr(character, attr) { let rollExpr = '@{' + character.get('name') + '|' + attr + '}'; return TrapTheme.rollAsync(rollExpr) .then((roll) => { if(roll) return roll.total; else throw new Error('Could not resolve roll expression: ' + rollExpr); }); } ... /** * Asynchronously rolls a dice roll expression and returns the result's total in * a promise. The result is undefined if an invalid expression is given. * @param {string} expr * @return {Promise<int>} */ static rollAsync(expr) { return new Promise((resolve, reject) => { sendChat('TrapTheme', '/w gm [[' + expr + ']]', (msg) => { try { let results = msg[0].inlinerolls[0].results; resolve(results); } catch(err) { reject(err); } }); }); } }; })(); For the most part this works, unless other attributes in the attribute's autocalc expression are also autocalculated and have dependencies that have not been initialized (set to something other than their default value).
1482943055

Edited 1482943078
Just curious because I haven't tried it myself. Can you use sendChat to perform the calculation and have the callback return the result to the original method? For example:    function foo() {      var res = sendChat("API", "/r 1d20", null, function(ops) {        return ops[0].inlinerolls[0].results.total      });      log(res); // And have res not be undefined?    }
1482943173

Edited 1482943894
Stephen L.
Pro
Marketplace Creator
Sheet Author
API Scripter
Kyle G. said: Just curious because I haven't tried it myself. Can you use sendChat to perform the calculation and have the callback return the result to the original method? For example:    function foo() {      var res = sendChat("API", "/r 1d20", null, function(ops) {        return ops[0].inlinerolls[0].results.total      });      log(res); // And have res not be undefined?    } No, the inline rolls in the sendChat expression are resolved asynchronously. You could at best have foo return the result of the roll in a Promise. E.G.: function foo() { return new Promise(resolve => { sendChat("API", "/r 1d20", ops => { let total = ops[0].inlinerolls[0].results.total; resolve(total); }); }); } // In another function: foo() .then(res => { // Do something with your roll result "res". });
1482988080

Edited 1482988095
Brian
Sheet Author
API Scripter
Stephen L. said: Yeah, I guess if getAttrByName() became asynchronous, that would break a lot of scripts using the current synchronous version. Well, it would be simple enough to make the function synchronous or asynchronous depending on whether a callback was passed to it, as I demonstrated. get() functions that way, for example. At the very least, that approach wouldn't break existing scripts, but it also doesn't give you a synchronous means of resolving the expression. You could write your own expression parser which was synchronous, but that would be a lot of effort. =P