The OSRchivist

Rediscovering the magic of old school roleplaying.

FoundryVTT Nested Table Macro

I’ve been preparing for my upcoming playthrough of Moonbase Blues for Mothership using FoundryVTT. To prepare, I was reviewing the thread on Moonbase Blues over on the Mothership Discord, when I saw this beauty:

Create 5 or 10 NPCs on base (MMC’s) for them to talk t o if they catch them lucid–what is their job/research/personality/what do they know?

I realized that this is exactly the type of thing that I struggle with to do on the fly when I Warden. So, I wanted to solve part of this issue with random tables!

Now, at first, I thought it would be easy to do using FoundryVTT’s built-in ability to do nested tables. That may still be possible, but the instructions on that page were outdated, as far as I could tell. Sure, I could have a table that rolled on a variety of other tables, but I couldn’t get anything to work to have it spit out extra text. At the bottom, though, I found that there was an API reference document.

Now, I’m not a coder, but I can dabble around in Python and C/C++, and get some stuff working in Bash on the command line. So, I figured there should be a way to script exactly what I wanted: combining (1) a first name, with (2) a last name, a (3) job, and a (4) project that the MMC worked at. I’d already set up those tables, so all I needed was a script that could select from them. With some searching on how JavaScript syntax worked, I managed to work up the following thing:

const firstNameTable = await game.tables.getName("MMC First Names");
const lastNameTable = await game.tables.getName("MMC Last Names")
const jobTable = await game.tables.getName("MMC Jobs");
const astrophysicsTable = await game.tables.getName("Astrophysics Projects");
const botanicalTable = await game.tables.getName("Botanical Projects");
const communicationsTable = await game.tables.getName("Communications Projects");
const geologicalTable = await game.tables.getName("Geological Projects");

// Construct the name
let firstName = await firstNameTable.roll();
let lastName = await lastNameTable.roll();
let name = firstName.results[0].name + " " + lastName.results[0].name;

// Find the job
let jobResult = await jobTable.roll();
let job = jobResult.results[0].name;

// Select a project
let projectResult = await astrophysicsTable.roll();
var astroProject = projectResult.results[0].name;
projectResult = await botanicalTable.roll();
var botProject = projectResult.results[0].name;
projectResult = await communicationsTable.roll();
var commsProject = projectResult.results[0].name;
projectResult = await geologicalTable.roll();
var geoProject = projectResult.results[0].name;

let roll = await new Roll(`1d2`).roll();
let quadRoll = await new Roll(`1d4`).roll();

switch (job) {
  case "Telescope Operator":
  case "Satellite Systems Engineer":
    if (roll.total == 1) {
      var project = astroProject;
    } else {
      var project = commsProject;
    }
    break;
  case "Lead Astronomer":
    var project = astroProject;
    break;
  case "Botanist/Greenhouse Manager":
  case "Hydroponics Specialist":
    var project = botProject;
    break;
  case "Geologist":
  case "Materials Scientist":
    var project = geoProject;
    break;
  case "Meteorite Analyst":
    if (roll.total == 1) {
      var project = astroProject;
    } else {
      var project = geoProject;
    }
    break;
  case "Communications Technician":
    var project = commsProject;
    break;
  case "Chief Engineer":
  case "Equipment Maintenance Technician":
  case "Data Analyst":
  case "Electrical Engineer":
  case "Research Coordinator":
  case "Laboratory Technician":
    if (quadRoll.total == 1) {
      var project = astroProject;
    } else if (quadRoll.total == 2) {
      var project = botProject;
    } else if (quadRoll.total == 3) {
      var project = commsProject;
    } else {
      var project = geoProject;
    }
    break;
}

// Construct and communicate the outcome
var results_html = `<h3>${name}</h3>
  <p>${job}`;
if (typeof project !== 'undefined') {
  results_html += `, working on the <strong>${project}</strong> project.`;
}
results_html += `<p>`;

ChatMessage.create({
  user: game.user._id,
  content: results_html
});

I had to learn that let and const could only define things inside of a scope (such as the curly brackets). So, I had to use var in some locations to make sure that the project selection could be used wider than my selection. A cool thing I learned is that the switch statement allowed me to chain multiple case statements one after the other, to capture certain common jobs, like a Data Analyst, that could work on any of the projects.

It was quite educational to make this, and I hope it’ll help me feel more free to improvise what they know if I already know who they are. I hope this can help out somebody else running this too!