- Anatomy of a Sheet Worker
- Events, and watching Attributes
- Variables – How to Name Things
- Arithmetic in Sheet Workers
- What If? in Sheet Workers
- JavaScript Objects
- Getting Loopy With JavaScript
- Logging in the Browser Console
- Strings, Arrays, and Loops
- Asynchronicity and Things to Avoid With Loops
- Changes and the eventInfo Object
- Action Buttons
- setAttrs and Saving Attributes
- Castle Falkenstein Design – Sheet Workers
- The Perils of Sheet Worker Functions
- The Script Block and Identifying Characters
- Arrays and Dropdowns
- Undefined and Other Error Values
- The Ternary Operator – The One-Line If
- Template Literals
- Functions and the Fat Arrow
- Strings in Sheet Workers
- A Sheet Worker Reprise
Arrays are a very flexible data type, and in this post, we will see how it is used and also see them being used in a fairly common situation. First, here’s a detailed example of building an array of statistic names.
const stat_array = [];
// whenever you add to an array you can use push or pop
stat_array.push('Strength');
stat_array.push('Dexterity');
// push adds to the end of an array (unshift adds to the start).
// if you examine the array, it will now be ['Strength', 'Dexterity']
// you can also use the key (aka index) to add directly, like
stat_array[2] = 'Constitution';
stat_array[3] = 'Intelligence';
// keys start at 0, so first item is [0], 2nd is [1], and so on.
// if you use a key that is already defined, it overwrites that value
stat_array[0] = 'Constitution';
// would transform ['Strength', 'Dexterity'] into ['Constitution', 'Dexterity']
// so be careful about using keys. Push always adds to the end of existing values.
// you can also create an array directly, like this:
const stat_names = ['Strength', 'Dexterity', 'Constitution', 'Intelligence', 'Wisdom', 'Charisma'];
const fifth_item = stat_names[4];
// fifth_item would equal 'Wisdom'
Code language: JavaScript (javascript)
Selecting a Stat Bonus
Now to use this practically. First, imagine you have this on your character sheet.
<input type="text" name="attr_weapon_name" placeholder="Weapon">
<span>BAB:</span>
<span name="attr_bab" value="0"></span>
<select name="attr_stat_mod">
<option value="@{str}">Strength</strength>
<option value="@{dex}">Dexterity</strength>
<option value="@{con}">Constitution</strength>
<option value="@{int}">Intelligence</strength>
<option value="@{wis}">Wisdom</option>
<option value="@{cha}">Charisma</option>
</select>
<span>Attack Total</span>
<input type="number" name="attr_attack" value="@{bab}+@{stat_mod}" disabled>
<button type="roll" name="roll_attack" value="/roll 1d20+@{attack}"></button>
Code language: HTML, XML (xml)
Here you can name a weapon, the bab (base attack bonus) is filled in automatically, then you pick a stat, and an attack total is calculated based on the bab and your stat bonus. You can then click a button to make that attack. Without any styling, that would look like this (it’s not pretty):
That would give a roll like this:
But what if you wanted to include other information based on the stat chosen, like the full name of the attribute? That’s difficult to do, since the stat selection gives you only the stat bonus. But you can do that with an array and a sheet worker. First, the basics.
Using an Array in a Sheet Worker
Here we set the values of the selected with a stat name we can use in the sheet worker. The other change here is we create a hidden attack input, which will hold the button roll text. Our sheet worker will calculate that.
<input type="text" name="attr_weapon_name" placeholder="Weapon">
<span>BAB:</span>
<span name="attr_bab" value="0"></span>
<select name="attr_stat_mod">
<option value="n/a" selected>Pick One</option>
<option value="str">Strength</option>
<option value="dex">Dexterity</option>
<option value="con">Constitution</option>
<option value="int">Intelligence</option>
<option value="wis">Wisdom</option>
<option value="cha">Charisma</option>
</select>
<span>Attack Total</span>
<input type="number" name="attr_attack_bonus" value="0" readonly>
<input type="hidden" name="attr_attack" value="Attack Not Defined">
<button type="roll" name="roll_attack" value="@{attack}"></button>
Code language: HTML, XML (xml)
The Sheet Worker
This sheet worker is doing two things: it is changing the way the visible bonus for the stat is calculated (see the attack_bonus attribute), and is creating an attack macro to be put in the button (roll_attack). There’s a lot going on in the sheet worker for this, but don’t worry, we’ll break it down bit by bit. But first, let’s look at the finished product.
on('change:stat_mod', function() {
const stats = ['str', 'dex', 'con', 'int', 'wis', 'cha'];
const stat_names = ['Strength', 'Dexterity', 'Constitution', 'Intelligence', 'Wisdom', 'Charisma'];
getAttrs(['stat_mod', ...stats], function(values) {
const stat = values.stat_mod;
const bonus = +values[stat] || 0;
let button_text = 'Attack Not Defined';
if(stats.includes(stat)) {
const stat_key = stats.indexOf(stat);
button_text = "&{template:default} {{name=@{weapon_name}}} {{Stat=" + stat_names[stat_key] + "}}} {{Attack=[[1d20+@{bab}+@{" +stat + "}]]}}";
}
setAttrs({
attack: button_text,
attack_bonus: bonus
});
});
});
Code language: JavaScript (javascript)
Now, to break it down and explain what’s going on in each part.
on('change:stat_mod', function() {
const stats = ['str', 'dex', 'con', 'int', 'wis', 'cha'];
const stat_names = ['Strength', 'Dexterity', 'Constitution', 'Intelligence', 'Wisdom', 'Charisma'];
getAttrs(['stat_mod', ...stats], function(values) {
Code language: JavaScript (javascript)
We will need the stat names array later, and we need to check which stat was selected. Since a sheet worker does not know anything about the contents of a character sheet, we need to give it the attributes of all stats. We could create an array like this:
getAttrs(['stat_mod', 'str', 'dex', 'con', 'int', 'wis', 'cha'],
Code language: JavaScript (javascript)
But since we already have an array of stats, we can use the spread operator (…). That breaks an array into its individual parts – it ‘spreads’ them out. So we can do this instead:
const stats = ['str', 'dex', 'con', 'int', 'wis', 'cha'];
getAttrs(['stat_mod', ...stats],
Code language: JavaScript (javascript)
Using arrays like this can be very handy.
Now we have to grab the attribute values we need. They are in the values object, but it is handy to break them out into variables we can see.
const stat = values.stat_mod;
const bonus = +values[stat] || 0;
let button_text = 'Attack Not Defined';
Code language: JavaScript (javascript)
The first two variables are unchanging constants, so we use the const keyword. button_text can change, as you’ll see, so we use let for that. Remember, if in doubt, just use let.
stat will be the value of the select dropdown, so str, dex, con, etc. We can use that to get the stat value, by using values[stat]. When you use values.stat, it looks for an attribute named ‘stat’, but that’s not what we want. Putting the stat inside [ ] means that JS treats this as a variable, a dynamic rating, which can change during the sheet worker.
We wrap that in +item || 0 to make sure it is a number. +item forces the variable into a number if possible, and || 0 gives it a value of 0 when it is not a number.
Finally, we give button_text a default value – Attack Not Defined.
if(stats.includes(stat)) {
Code language: JavaScript (javascript)
We have done a post on if statements, but we haven’t seen stats.includes(stat). Includes is a special function that will check an array to see if something exists in it, and gives a value of true if found, and false if not. Use array_name.includes(item) to see if an item is in the array. It will look for a perfect match – so it will not match ‘str’ with ‘strength’, nor ‘str’ with ‘Str’ – there are ways to cope with that, but we’ll deal with that on the post about strings.
So if stat exists in stats, aka if stats includes stat, the following code runs. Otherwise it is skipped.
const stat_key = stats.indexOf(stat);
button_text = "&{template:default} {{name=@{weapon_name}}} {{Stat=" + stat_names[stat_key] + "}}} {{Attack=[[1d20+@{bab}+@{" +stat + "}]]}}";
Code language: JavaScript (javascript)
indexOf is another handy function. It matches an item in an array just as with includes, but also tells you its key. So if the stat is ‘str’, it will return a key of 0 (remember, arrays start at 0), and since the stats and stat_names arrays are ordered the same way, you can use the same key to get the corresponding value from each. That means stat_names[stat_key] will give the full name (in this case ‘Strength’.
You can use + to join strings together. “stat” + ” name” = “stat name”. (Notice the space – this method doesn’t add spaces automatically.)
So with the stat_key we can insert the stat_name, and construct out attack macro.
setAttrs({
attack: button_text,
attack_bonus: bonus
});
Code language: JavaScript (javascript)
Finally, we save those back to our character sheet with setAttrs.
Note that attack and attack_bonus both have default values (‘Attack Not Defined’ and 0, respectively) and this ensures the defaults will overwrite old values if the stat is not recognised. Look at the first option in the select dropdown!
And there we have it. We described arrays and some of the most common methods used with them. We’ll be revisiting them in the next sheet worker post on loops.
Summary
Here are the methods used again, and some related methods you might want to explore that will be described in future posts.
- Create arrays with [ ]
- You can read the contents of an array with its key (array[0]).
- Add to the end of an array with .push()
- Extract from the start of an array with .shift() (this deletes items from the start of an array)
- Take from the end with .pop(), and add to the start with .unshift()
- array_name.includes(item) checks if item exists in an array (true/false);
- array.indexOf(item) returns the key an item is found, and returns -1 if not found.
- array.length tells you how many items there are; remember the last item is at length -1, because keys start at 0.
- Can use negative lengths to count backwards, so array.at[-1] gives the last entry.
- String(array) or array.join(‘,’) creates a list
- … spread operator breaks an array into items