D&D Player Race Heights

Here’s the finished product rendered with D31:

Height statistics data for D&D races, alphabetized. Heights are in inches. Hover over a bar to see a bar's specific value.

As it turns out, three Kobolds in a trench coat could conceivably pass as a Dragonborn—give or take.

The Original Sheet

In building the original version of the sheet, I wanted to plug in that when rolling traits for a Human, Humans have a Base Height of 57”, and add 2d10 to get their real height. From those inputs, I could then build the entire spreadsheet dynamically.

I stuck all the info I gathered into a CSV:

Race,Base Height,Height Dice
Human,56,2d10
Elf (High),54,2d10
Orc,64,2d8
...

I kept the Height Dice as dice notation strings. Keeping them in dice notation meant I could do this, using custom dice statistics macros when I uploaded the CSV into Google Sheets:

A B C D E F
  int string = B + DICE_MIN(C) = B + DICE_MAX(C) = B + DICE_MEAN(C)
Human 56 2d10 58 76 67
Elf (High) 54 2d10 56 74 65
Orc 64 2d8 66 80 73
Race Base Height (in) Height Dice Rollable Min (in) Rollable Max (in) Rollable Mean (in)

I flash-filled the whole sheet for the other player races, and it worked!

I backed out of using the macros for their performance issues in the final Sheets version, since I could reformulate the functions based on Height Dice size, and not call the macros. They were overkill for a chart where all rolls had the same dice count, and they ended up adding a significant amount of init time just to load the sheets as the macros were blocking calls for most other columns.

Dice Functions

To dynamically fill the sheet using the dice notations, I built a few Apps Script2 functions within Google Sheets:

/**
 * Returns the minimum result possible from a dice roll.
 *
 * @param {string} input The dice notation string in the format XdY.
 * @return The minimum result possible.
 * @customfunction
 */
function DICE_MIN(input) {
    count = input.split("d").map(Number)[0]
    if (count === 0) count = 1;
    return count;
}

/**
 * Returns the maximum result possible from a dice roll.
 *
 * @param {string} input The dice notation string in the format XdY.
 * @return The maximum result possible.
 * @customfunction
 */
function DICE_MAX(input) {
    parts = input.split("d").map(Number);
    if (parts[0] === 0) parts[0] = 1;
    return parts[0] * parts[1];
}

/**
 * Returns the mean result possible from a dice roll.
 *
 * @param {string} input The dice notation string in the format XdY.
 * @return The mean result possible.
 * @customfunction
 */
function DICE_MEAN(input) {
    parts = input.split("d").map(Number)
    if (parts[0] === 0) parts[0] = 1;
    return (1 + parts[1]) / 2 * parts[0];
}

There’s some potential bugs if given bad inputs, but to give examples of the functions in use:

input DICE_MIN DICE_MAX DICE_MEAN
1d10 1 10 5.5
2d10 2 20 11
d20 1 20 10.5
4d6 4 24 14
2d100 2 200 101

Macros used in Google Sheets must be deterministic—given a particular input, the function must always produce the same output—so they’re perfect for statistical functions. A method like DICE_ROLL() to roll a dice notation can’t be implemented, or would have some bogus results.

  1. View the Google Sheets version of the chart

  2. Apps Script is just JavaScript.