Skip to content

Commit fa934a0

Browse files
authored
Merge pull request #2069 from Roll20/beacon/char-sheet-utils
CharSheetUtils | 1.1 | Beacon update
2 parents c6d1b9a + d560de3 commit fa934a0

3 files changed

Lines changed: 208 additions & 45 deletions

File tree

CharSheetUtils/1.1/index.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
* Create the CharSheetUtils library. All the functionality of this library
3+
* is exposed through its static methods.
4+
*/
5+
var CharSheetUtils = (() => {
6+
'use strict';
7+
8+
return class {
9+
/**
10+
* Asynchronously gets the value of a character sheet attribute.
11+
* @param {Character} character
12+
* @param {string} attr
13+
* @return {Promise<number>}
14+
* Contains the value of the attribute.
15+
*/
16+
static async getSheetAttr(character, attr) {
17+
if(attr.includes('/'))
18+
return CharSheetUtils.getSheetRepeatingAttr(character, attr);
19+
else {
20+
return getSheetItem(character.id, attr).then((value) => {
21+
return value;
22+
});
23+
}
24+
}
25+
26+
/**
27+
* Asynchronously gets the value of a character sheet attribute from a
28+
* repeating row.
29+
* @param {Character} character
30+
* @param {string} attr
31+
* Here, attr has the format "sectionName/nameFieldName/nameFieldValue/valueFieldName".
32+
* For example: "skills/name/perception/total"
33+
* @return {Promise<number>}
34+
* Contains the value of the attribute.
35+
*/
36+
static getSheetRepeatingAttr(character, attr) {
37+
let parts = attr.split('/');
38+
if(parts.length < 4) return;
39+
40+
let sectionName = parts[0];
41+
let nameFieldName = parts[1];
42+
let nameFieldValue = parts[2].toLowerCase();
43+
let valueFieldName = parts[3];
44+
45+
// Find the row with the given name.
46+
return CharSheetUtils.getSheetRepeatingRow(character, sectionName, rowAttrs => {
47+
let nameField = rowAttrs[nameFieldName];
48+
if(!nameField)
49+
return false;
50+
return nameField.get('current').toLowerCase().trim() === nameFieldValue;
51+
})
52+
53+
// Get the current value of that row.
54+
.then(rowAttrs => {
55+
if(!rowAttrs)
56+
return NaN;
57+
58+
let valueField = rowAttrs[valueFieldName];
59+
if(!valueField)
60+
return NaN;
61+
return valueField.get('current');
62+
});
63+
}
64+
65+
/**
66+
* Gets the map of attributes inside of a repeating section row.
67+
* @param {Character} character
68+
* @param {string} section
69+
* The name of the repeating section.
70+
* @param {func} rowFilter
71+
* A filter function to find the correct row. The argument passed to it is a
72+
* map of attribute names (without the repeating section ID part - e.g. "name"
73+
* instead of "repeating_skills_-123abc_name") to their actual attributes in
74+
* the current row being filtered. The function should return true iff it is
75+
* the correct row we're looking for.
76+
* @return {Promise<any>}
77+
* Contains the map of attributes.
78+
*/
79+
static getSheetRepeatingRow(character, section, rowFilter) {
80+
// Get all attributes in this section and group them by row.
81+
let attrs = findObjs({
82+
_type: 'attribute',
83+
_characterid: character.get('_id')
84+
});
85+
86+
// Group the attributes by row.
87+
let rows = {};
88+
_.each(attrs, attr => {
89+
let regex = new RegExp(`repeating_${section}_(-([0-9a-zA-Z\-_](?!_storage))+?|\$\d+?)_([0-9a-zA-Z\-_]+)`);
90+
let match = attr.get('name').match(regex);
91+
if(match) {
92+
let rowId = match[1];
93+
let attrName = match[3];
94+
if(!rows[rowId])
95+
rows[rowId] = {};
96+
97+
rows[rowId][attrName] = attr;
98+
}
99+
});
100+
101+
// Find the row that matches our filter.
102+
return Promise.resolve(_.find(rows, rowAttrs => {
103+
return rowFilter(rowAttrs);
104+
}));
105+
}
106+
107+
/**
108+
* Asynchronously rolls a dice roll expression and returns the roll result
109+
* in a Promise.
110+
* @param {string} expr
111+
* @return {Promise<RollResult>}
112+
*/
113+
static rollAsync(expr) {
114+
return new Promise((resolve, reject) => {
115+
sendChat('CharSheetUtils', '/w gm [[' + expr + ']]', (msg) => {
116+
try {
117+
let results = msg[0].inlinerolls[0].results;
118+
resolve(results);
119+
}
120+
catch(err) {
121+
log(expr);
122+
reject(err);
123+
}
124+
});
125+
});
126+
}
127+
128+
static send(text) {
129+
sendChat('API', '' + `<div>${text}</div>`, null, {noarchive:true});
130+
}
131+
132+
static async handleInput(msg) {
133+
let args = msg.content.split(' ');
134+
let command = args.shift().substring(1);
135+
let extracommand = args.shift();
136+
137+
if (command === 'roll') {
138+
let t = await CharSheetUtils.rollAsync(extracommand);
139+
CharSheetUtils.send(t);
140+
}
141+
142+
else if(command === 'getattr') {
143+
if(!msg.selected) return;
144+
for (const s of msg.selected) {
145+
if (s._type !== 'graphic') continue;
146+
147+
let token = getObj('graphic', s._id);
148+
if (!token) continue;
149+
let character = getObj('character', token.get('represents'));
150+
151+
let t = await CharSheetUtils.getSheetAttr(character, extracommand);
152+
CharSheetUtils.send(t);
153+
}
154+
}
155+
}
156+
};
157+
})();
158+
159+
on('ready',function() {
160+
'use strict';
161+
162+
on('chat:message', CharSheetUtils.handleInput);
163+
});

CharSheetUtils/script.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"name": "Character Sheet Utils",
33
"script": "index.js",
4-
"version": "1.0",
5-
"previousversions": [],
4+
"version": "1.1",
5+
"previousversions": [
6+
"1.0"
7+
],
68
"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",
79
"authors": "Ada Lindberg",
810
"roll20userid": 46544,
911
"useroptions": [],
1012
"dependencies": [],
1113
"modifies": {}
12-
}
14+
}

CharSheetUtils/src/index.js

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,19 @@ var CharSheetUtils = (() => {
66
'use strict';
77

88
return class {
9-
/**
10-
* Attempts to force a calculated attribute to be corrected by
11-
* setting it.
12-
* @param {Character} character
13-
* @param {string} attr
14-
*/
15-
static forceAttrCalculation(character, attr) {
16-
// Attempt to force the calculation of the attribute by setting it.
17-
createObj('attribute', {
18-
_characterid: character.get('_id'),
19-
name: attr,
20-
current: -9999
21-
});
22-
23-
// Then try again.
24-
return CharSheetUtils.getSheetAttr(character, attr)
25-
.then(result => {
26-
if(_.isNumber(result))
27-
return result;
28-
else
29-
log('Could not calculate attribute: ' + attr + ' - ' + result);
30-
});
31-
}
32-
339
/**
3410
* Asynchronously gets the value of a character sheet attribute.
3511
* @param {Character} character
3612
* @param {string} attr
3713
* @return {Promise<number>}
3814
* Contains the value of the attribute.
3915
*/
40-
static getSheetAttr(character, attr) {
16+
static async getSheetAttr(character, attr) {
4117
if(attr.includes('/'))
4218
return CharSheetUtils.getSheetRepeatingAttr(character, attr);
4319
else {
44-
let rollExpr = '@{' + character.get('name') + '|' + attr + '}';
45-
return CharSheetUtils.rollAsync(rollExpr)
46-
.then((roll) => {
47-
if(roll)
48-
return roll.total;
49-
else
50-
throw new Error('Could not resolve roll expression: ' + rollExpr);
51-
})
52-
.then(value => {
53-
if(_.isNumber(value))
54-
return value;
55-
56-
// If the attribute is autocalculated, but could its current value
57-
// could not be resolved, try to force it to calculate its value as a
58-
// last-ditch effort.
59-
else
60-
return CharSheetUtils.forceAttrCalculation(character, attr);
20+
return getSheetItem(character.id, attr).then((value) => {
21+
return value;
6122
});
6223
}
6324
}
@@ -74,6 +35,8 @@ var CharSheetUtils = (() => {
7435
*/
7536
static getSheetRepeatingAttr(character, attr) {
7637
let parts = attr.split('/');
38+
if(parts.length < 4) return;
39+
7740
let sectionName = parts[0];
7841
let nameFieldName = parts[1];
7942
let nameFieldValue = parts[2].toLowerCase();
@@ -161,5 +124,40 @@ var CharSheetUtils = (() => {
161124
});
162125
});
163126
}
127+
128+
static send(text) {
129+
sendChat('API', '' + `<div>${text}</div>`, null, {noarchive:true});
130+
}
131+
132+
static async handleInput(msg) {
133+
let args = msg.content.split(' ');
134+
let command = args.shift().substring(1);
135+
let extracommand = args.shift();
136+
137+
if (command === 'roll') {
138+
let t = await CharSheetUtils.rollAsync(extracommand);
139+
CharSheetUtils.send(t);
140+
}
141+
142+
else if(command === 'getattr') {
143+
if(!msg.selected) return;
144+
for (const s of msg.selected) {
145+
if (s._type !== 'graphic') continue;
146+
147+
let token = getObj('graphic', s._id);
148+
if (!token) continue;
149+
let character = getObj('character', token.get('represents'));
150+
151+
let t = await CharSheetUtils.getSheetAttr(character, extracommand);
152+
CharSheetUtils.send(t);
153+
}
154+
}
155+
}
164156
};
165157
})();
158+
159+
on('ready',function() {
160+
'use strict';
161+
162+
on('chat:message', CharSheetUtils.handleInput);
163+
});

0 commit comments

Comments
 (0)