I advocate for solo gaming and zero-prep gaming all the time. With the advent of VTTs, one thing is the ability to use macros and other tech to make zero-prep gaming more straightforward and streamlined. I recently asked Grok to help me code simple scripts that automate things with a single click. Since AI made them, and I did not, I wanted to share them with you because they seem to work. Maybe they can enrich your games.
How to Use Them
Open Foundry VTT and go to whatever world you want to use these in. Copy the code for each macro into the bar along the bottom that looks like this:
Click on the number to bring up the window where you can paste the code. Set the “type” to “script,” as shown below.
Execute the macro to see if it works or just “save the macro.” Clicking that macro will execute the script and output the result to the chat in Foundry.
Terrain Generation Script
This script will generate a terrain type based on the kind present in OSE (Old School Essentials). It will check to see if there is a settlement in the hex. If there is no settlement, it will check whether there is an underground feature (like a dungeon or cave) or a wilderness feature (like a moss-covered ruin). With a click, it outputs the result like this in the Foundry chat:
Here is the script to past into the macro:
// Roll helper function
function rollDie(sides) {
return Math.ceil(Math.random() * sides);
}
// Terrain Types Table
const terrainTypesTable = [
"Clear", "Grassland", "Barren Lands", "Hills", "Mountains", "Desert", "Jungle",
"Swamp", "Water", "Same as the Terrain you came from"
];
const terrainType = terrainTypesTable[rollDie(10) - 1];
// Settlement check (1 in 6 chance), only for non-Water terrain
let settlement = "No settlement.";
let settlementCheck = null;
if (terrainType !== "Water") {
settlementCheck = rollDie(6);
settlement = settlementCheck === 1 ? "Settlement found!" : "No settlement.";
}
// Underground feature check (1 in 6 chance)
const undergroundFeatureCheck = rollDie(6);
const undergroundFeature = undergroundFeatureCheck === 1 ? "Underground feature or cave found!" : "No underground feature.";
// Wilderness feature check (1 in 6 chance)
const wildernessFeatureCheck = rollDie(6);
const wildernessFeature = wildernessFeatureCheck === 1 ? "Wilderness feature found!" : "No wilderness feature.";
// Generate Description
let description = `<strong>Terrain Type:</strong> ${terrainType}<br>`;
description += `<strong>Settlement:</strong> ${settlement}<br>`;
if (terrainType !== "Water" && settlementCheck !== 1) {
description += `<strong>Underground Feature:</strong> ${undergroundFeature}<br>`;
if (undergroundFeatureCheck !== 1) {
description += `<strong>Wilderness Feature:</strong> ${wildernessFeature}<br>`;
}
}
// Output to chat
ChatMessage.create({
content: `<h2>Hex Terrain & Features Generator</h2>${description}`,
whisper: ChatMessage.getWhisperRecipients("GM") // Change "GM" as needed
});
Settlement Generation
This macro will generate and name a settlement and tell you the trade goods the settlement may have, a choice of government types, and population. It outputs the information like this in chat:
// Roll helper function
function rollDie(sides) {
return Math.ceil(Math.random() * sides);
}
// Settlement Size Table
const settlementTypesTable = [
{ name: "Homestead", minPop: 1, maxPop: 12 },
{ name: "Thorp", minPop: 20, maxPop: 80 },
{ name: "Hamlet", minPop: 100, maxPop: 400 },
{ name: "Village", minPop: 600, maxPop: 900 },
{ name: "Town", minPop: 1500, maxPop: 6500 },
{ name: "City", minPop: 10000, maxPop: 60000 }
];
// Generate Settlement Type
const settlementType = settlementTypesTable[rollDie(6) - 1];
const population = rollDie(settlementType.maxPop - settlementType.minPop + 1) + settlementType.minPop;
// Settlement Name Generation
const namePrefix = ["Green", "Silver", "Red", "Black", "White", "Golden", "Storm", "Wood", "Crystal", "Iron",
"Moon", "Sun", "Bright", "Shadow", "Stone", "Wild", "River", "East", "West", "North",
"South", "Clear", "Dawn", "Night", "Sky"];
const nameSuffix = ["burg", "hollow", "shire", "dale", "ton", "ville", "cross", "ford", "wood", "field",
"town", "spring", "crest", "rock", "gate", "hill", "mere", "watch", "cliff", "point",
"brook", "stone", "glade", "haven"];
const settlementName = namePrefix[rollDie(namePrefix.length) - 1] + nameSuffix[rollDie(nameSuffix.length) - 1];
// Government Type Generation
const governmentTypes = [
"Monarchy", "Republic", "Theocracy", "Feudal Lordship", "Democracy", "Oligarchy"
];
let governmentType = "";
if (settlementType.name === "Homestead") {
governmentType = "Independent (No government)";
} else if (settlementType.name === "Thorp") {
governmentType = "Feudal Lordship";
} else if (settlementType.name === "Hamlet") {
governmentType = "Feudal Lordship or Local Council";
} else if (settlementType.name === "Village") {
governmentType = "Feudal Lordship or Mayor/Council";
} else if (settlementType.name === "Town") {
governmentType = "Republic or Mayor/Council";
} else if (settlementType.name === "City") {
governmentType = "Republic, Oligarchy, or Theocracy";
}
// Trade Goods Generation
const tradeGoods = [
"Grain", "Timber", "Wine", "Furs", "Iron", "Salt", "Cloth", "Herbs", "Pottery", "Weapons",
"Fish", "Stone", "Livestock", "Spices", "Jewels", "Wine", "Tools", "Candles", "Leather"
];
const tradeGoodsTable = [];
let numGoods = 0;
// Determine trade goods based on population
if (settlementType.name === "Homestead") numGoods = 1;
else if (settlementType.name === "Thorp") numGoods = 2;
else if (settlementType.name === "Hamlet") numGoods = 3;
else if (settlementType.name === "Village") numGoods = 4;
else if (settlementType.name === "Town") numGoods = 5;
else if (settlementType.name === "City") numGoods = 6;
for (let i = 0; i < numGoods; i++) {
const randomGood = tradeGoods[rollDie(tradeGoods.length) - 1];
if (!tradeGoodsTable.includes(randomGood)) {
tradeGoodsTable.push(randomGood);
} else {
i--; // Avoid duplicates
}
}
// Generate Settlement Description
let description = `<strong>Settlement Name:</strong> ${settlementName}<br>`;
description += `<strong>Settlement Type:</strong> ${settlementType.name} (Population: ${population})<br>`;
description += `<strong>Government Type:</strong> ${governmentType}<br>`;
description += `<strong>Trade Goods:</strong> ${tradeGoodsTable.join(", ")}<br>`;
// Output to chat
ChatMessage.create({
content: `<h2>Settlement Generator</h2>${description}`,
whisper: ChatMessage.getWhisperRecipients("GM") // Change "GM" as needed
});
Settlement Trade and Economics
This macro will allow you to input the information from the previous macro outputs and determine whether the settlement economically experiences a boom or a bust. Usually, the trade goods the settlement is reasonable at and produces will be cheaper than those it is not good at making. Clicking the macro creates the window below. Select the information the previous settlement generator makes and get more if needed.
For the settlement of Westton that was generated above, I would input the info in the window like this after clicking the macro:
And it would output this information for Westton:
Weapons will be 10% cheaper in Westton. Jewels will also be bought and sold here at a 2% lower price/cost.
// Define trade goods and their base prices
const tradeGoods = [
{ name: "Grain", price: 5 },
{ name: "Timber", price: 10 },
{ name: "Wine", price: 15 },
{ name: "Furs", price: 20 },
{ name: "Iron", price: 25 },
{ name: "Salt", price: 30 },
{ name: "Cloth", price: 35 },
{ name: "Herbs", price: 40 },
{ name: "Pottery", price: 45 },
{ name: "Weapons", price: 50 },
{ name: "Fish", price: 55 },
{ name: "Stone", price: 60 },
{ name: "Livestock", price: 65 },
{ name: "Spices", price: 70 },
{ name: "Jewels", price: 75 },
{ name: "Tools", price: 80 },
{ name: "Candles", price: 85 },
{ name: "Leather", price: 90 }
];
// Determine boom or bust
const boomBustChance = Math.random();
const isBoom = boomBustChance < 0.05; // 5% chance of boom
const isBust = boomBustChance >= 0.95; // 5% chance of bust
// Calculate price modifiers
const inflationModifier = isBoom ? -0.2 : isBust ? 0.2 : Math.random() * 0.1 + 0.05; // Base inflation/deflation
const weaponModifier = isBoom ? -0.3 : isBust ? 0.3 : Math.random() * 0.2 - 0.1;
const jewelModifier = isBoom ? -0.4 : isBust ? 0.4 : Math.random() * 0.2 - 0.1;
// Create the form
new Dialog({
title: "Settlement Trade and Population",
content: `
<form>
<div class="form-group">
<label>Settlement Type:</label>
<input type="text" id="settlement-type" />
</div>
<div class="form-group">
<label>Population:</label>
<input type="number" id="population" />
</div>
<div class="form-group">
<label>Government Type:</label>
<input type="text" id="government-type" />
</div>
<div class="form-group">
<label>Trade Goods:</label>
${tradeGoods
.map(
(good, index) =>
`<label><input type="checkbox" id="good-${index}" value="${good.name}" /> ${good.name}</label><br>`
)
.join("")}
</div>
</form>
`,
buttons: {
calculate: {
label: "Calculate",
callback: (html) => {
const settlementType = html.find("#settlement-type").val();
const population = parseInt(html.find("#population").val());
const governmentType = html.find("#government-type").val();
const selectedGoods = tradeGoods.filter((_, index) =>
html.find(`#good-${index}`).is(":checked")
);
const fightingMen = Math.floor(population / 15);
// Map all trade goods regardless of whether they are selected or not
const tradePrices = tradeGoods.map((good) => {
const priceModifier = good.name === "Weapons" ? weaponModifier :
good.name === "Jewels" ? jewelModifier : inflationModifier;
return `${good.name}: ${(good.price * (1 + parseFloat(priceModifier))).toFixed(2)} gp`;
});
// Add weapon and jewel modifiers for display
const weaponChange = `Weapons: ${(weaponModifier * 100).toFixed(0)}%`;
const jewelChange = `Jewels: ${(jewelModifier * 100).toFixed(0)}%`;
// Output to chat
ChatMessage.create({
content: `
<h2>Settlement Trade Report</h2>
<strong>Settlement Type:</strong> ${settlementType}<br>
<strong>Population:</strong> ${population}<br>
<strong>Government Type:</strong> ${governmentType}<br>
<strong>Fighting Men:</strong> ${fightingMen}<br>
<strong>Trade Prices:</strong><br>
${tradePrices.map((price) => `- ${price}`).join("<br>")}<br>
- ${weaponChange}<br>
- ${jewelChange}<br>
<strong>Economic Condition:</strong> ${isBoom ? "Boom" : isBust ? "Bust" : "Stable"}
`,
whisper: ChatMessage.getWhisperRecipients("GM")
});
}
},
close: {
label: "Close"
}
},
default: "calculate"
}).render(true);
Wilderness Descriptions
This script will generate a wilderness location that can be discovered. It will output it to chat like this:
// Roll helper function
function rollDie(sides) {
return Math.ceil(Math.random() * sides);
}
// Wilderness Site Types Table
const siteTypesTable = [
"Ruins", "Ancient Statue", "Abandoned Camp", "Cave Entrance", "Mysterious Shrine",
"Fallen Tree", "Forest Clearing", "Old Battlefield", "Stone Circle", "Overgrown Tower",
"Crystal Formation", "Sacred Grove", "Boulder Field", "Hidden Waterfall", "Sunken Temple",
"Moss-Covered Obelisk", "Witch's Hut", "Fungus Forest", "Desert Oasis", "Rocky Outcrop"
];
const siteType = siteTypesTable[rollDie(20) - 1];
// Additional Detail Tables (some with multiple possible outcomes)
const ruinConditionTable = [
"Collapsed", "Well-Preserved", "Overgrown with Vines", "Crumbled into Pieces", "Ruined by Fire"
];
const ruinCondition = (siteType === "Ruins" || siteType === "Old Battlefield") ? ruinConditionTable[rollDie(5) - 1] : null;
const statueDescriptionTable = [
"A crumbling stone figure of a forgotten god", "A giant statue with broken limbs", "A pristine marble statue of a warrior",
"An ancient stone statue of a mystical creature", "A cracked stone statue of a long-dead king"
];
const statueDescription = siteType === "Ancient Statue" ? statueDescriptionTable[rollDie(5) - 1] : null;
const shrineFeatureTable = [
"An altar with offerings", "A series of eerie glowing runes", "An abandoned shrine overgrown with plants",
"A small pool of clear water", "A circle of stones arranged in a pattern"
];
const shrineFeature = siteType === "Mysterious Shrine" ? shrineFeatureTable[rollDie(5) - 1] : null;
const caveContentTable = [
"It leads deeper into a dark tunnel", "It's home to a bear or large creature", "Inside, a faint glow emanates from crystals",
"A group of goblins or bandits is hiding within", "The cave is empty, save for some scattered bones"
];
const caveContent = siteType === "Cave Entrance" ? caveContentTable[rollDie(5) - 1] : null;
const magicalEffectTable = [
"A faint aura of magic lingers", "Strange symbols glow faintly around the site", "The air feels unnaturally still and quiet",
"A low hum resonates from the ground", "You hear whispers when you get too close"
];
const magicalEffect = (siteType === "Sacred Grove" || siteType === "Mysterious Shrine" || siteType === "Stone Circle") ? magicalEffectTable[rollDie(5) - 1] : null;
// Generate Description
let description = `<strong>Wilderness Site Type:</strong> ${siteType}<br>`;
if (ruinCondition) {
description += `<strong>Condition:</strong> ${ruinCondition}<br>`;
}
if (statueDescription) {
description += `<strong>Statue Description:</strong> ${statueDescription}<br>`;
}
if (shrineFeature) {
description += `<strong>Shrine Feature:</strong> ${shrineFeature}<br>`;
}
if (caveContent) {
description += `<strong>Cave Content:</strong> ${caveContent}<br>`;
}
if (magicalEffect) {
description += `<strong>Magical Effect:</strong> ${magicalEffect}<br>`;
}
// Output to chat
ChatMessage.create({
content: `<h2>Wilderness Site Generator</h2>${description}`,
whisper: ChatMessage.getWhisperRecipients("GM") // Change "GM" as needed
});
Dungeon Name Generator
Dungeons need a good name, and this one will generate the name of a dungeon for you with one click in Foundry VTT.
// Define the columns for the dungeon name
const features = [
"Tomb", "Crypt", "Labyrinth", "Caverns", "Sanctuary",
"Fortress", "Temple", "Ruins", "Shrine", "Vault",
"Castle", "Stronghold", "Maze", "Citadel", "Keep",
"Chambers", "Catacombs", "Dungeon", "Manor", "Palace"
];
const descriptors = [
"Lost", "Cursed", "Forgotten", "Hidden", "Forbidden",
"Ancient", "Shadowed", "Haunted", "Abandoned", "Dark",
"Fabled", "Mystic", "Shrouded", "Blighted", "Desolate",
"Sunken", "Infernal", "Arcane", "Enchanted", "Ruined"
];
const subjects = [
"Kings", "Lords", "Queens", "Gods", "Heroes",
"Wizards", "Beasts", "Demons", "Dragons", "Titans",
"Giants", "Thieves", "Hunters", "Sorcerers", "Priests",
"Spirits", "Knights", "Warriors", "Vampires", "Rulers"
];
// Roll a d20 for each column
const feature = features[Math.floor(Math.random() * 20)];
const descriptor = descriptors[Math.floor(Math.random() * 20)];
const subject = subjects[Math.floor(Math.random() * 20)];
// Construct the dungeon name
const dungeonName = `The ${feature} of ${descriptor} ${subject}`;
// Display the result to the chat
ChatMessage.create({
content: `<h2>${dungeonName}</h2>`,
whisper: ChatMessage.getWhisperRecipients("GM") // Replace "GM" with specific users if needed
});
Dungeon Room Descriptions
This macro will give you a simple room description.
// Define the components for the room description
const roomAdjectives = [
"Dark", "Dusty", "Crumbling", "Damp", "Gloomy", "Ancient", "Eerie", "Chilly", "Shadowy", "Claustrophobic",
"Grand", "Narrow", "Overgrown", "Silent", "Echoing", "Foul-smelling", "Moss-covered", "Flooded", "Unstable", "Glowing",
"Rotten", "Frost-covered", "Luminous", "Moldy", "Winding", "Foggy", "Suffocating", "Desolate", "Ancient", "Twisted",
"Vast", "Soggy", "Abandoned", "Sinking", "Haunted", "Icy", "Cracked", "Overrun", "Forsaken", "Shattered",
"Misty", "Gritty", "Suffocating", "Creaking", "Dilapidated", "Sprawling", "Majestic", "Vibrant", "Monolithic",
"Soot-covered", "Cavernous", "Vibrant", "Frosted", "Musty", "Rotting", "Flickering", "Tattered", "Falling", "Teeming",
"Smoky", "Lush", "Barren", "Inverted", "Crushing", "Tangled", "Groaning", "Lonely", "Flickering", "Shimmering",
"Dampened", "Lurking", "Sinking", "Eclipsed", "Flaking", "Shivering", "Wretched", "Frostbitten", "Crumbled", "Torn",
"Emerging", "Faint", "Shifting", "Pulsing", "Beckoning", "Shaky", "Brimming", "Ancient", "Trembling", "Decrepit",
"Twisting", "Fallen", "Saturated", "Fuming", "Bubbling", "Crushing", "Eroded", "Creaking", "Suffocating", "Dying",
"Forgotten", "Ragged", "Thrumming", "Rising", "Crushing", "Unfolding", "Flickering", "Submerged", "Steaming", "Torn",
"Stifling", "Vibrating", "Strangling", "Humming", "Subdued", "Crackling", "Bitter", "Unstable", "Perilous", "Lopsided"
];
const roomTypes = [
"Chamber", "Hall", "Vault", "Shrine", "Passage", "Catacomb", "Sanctum", "Gallery", "Cell", "Throne Room",
"Library", "Barracks", "Crypt", "Workshop", "Dining Hall", "Guardroom", "Study", "Storeroom", "Laboratory", "Armory",
"Antechamber", "Basilica", "Cathedral", "Dungeon", "Observatory", "Sacristy", "Conclave", "Hallway", "Guardhouse", "Foyer",
"Alcove", "Crypt", "Bunker", "Refuge", "Furnace", "Chapel", "Annex", "Coliseum", "Atrium", "Cloister",
"Passageway", "Sepulcher", "Dais", "Parlor", "Loft", "Chasm", "Dungeons", "Subterranean", "Overlook", "Hallway",
"Temple", "Maze", "Sanctuary", "Vaults", "Ritual Chamber", "Workshop", "Labyrinth", "Furnace", "Warren",
"Grotto", "Aqueduct", "Arboretum", "Scriptorium", "Alcove", "Gallery", "Dome", "Cellar", "Tomb", "Garden",
"Furnishing", "Dais", "Salon", "Brewery", "Theater", "Lounge", "Cistern", "Mausoleum", "Cavern", "Doom",
"Keep", "Crypt", "Hideaway", "Lair", "Tunnels", "Hallway", "Turret", "Cave", "Emporium", "Storage",
"Vaulting", "Spire", "Pavilion", "Reservoir", "Haven", "Throne", "Tear", "Exile", "Dome", "Summoning",
"Enclave", "Hall", "Observatory", "Passage", "Vitrine", "Bastion", "Lurking", "Splay", "Enclave", "Resting"
];
const notableFeatures = [
"a collapsed wall", "an ancient altar", "a glowing rune", "a bloodstained floor", "a broken statue", "a pile of bones",
"a flickering torch", "a deep pit", "a strange glyph", "a rusted weapon", "a hidden door", "a chilling draft", "a cracked mirror",
"a pool of stagnant water", "a hanging chain", "a discarded book", "a tangled web", "an ominous shadow", "a forgotten mural",
"a mysterious sigil", "a hidden treasure chest", "a broken window", "a set of skeletal remains", "a pendulum swinging",
"a trapdoor", "a mosaic floor", "a darkened throne", "a strange pulsating light", "an altar of strange stone", "a shallow grave",
"a shrine to a forgotten god", "a portrait of a lost noble", "a pool of glowing liquid", "a flickering fireplace",
"an eerie hum", "a shifting shadow", "a series of paintings", "a broken column", "an abandoned weapon rack",
"a crumbling pillar", "a twisting stairwell", "a pile of old clothes", "a hidden trap", "a collection of old journals",
"a hidden passage", "an unfinished statue", "a web-covered book", "an ominous painting", "a dusty tapestry", "a false floor",
"an ancient rug", "a statue of a strange god", "a tangled mess of ropes", "a shattered vase", "a rusted cage", "a glowing crystal",
"a hollow chest", "a secret safe", "an ancient tome", "a pile of broken tools", "a faint musical tune", "a forgotten weapon",
"a cursed trinket", "a dark obsidian mirror", "a carved stone", "a missing floorboard", "a crack in the ceiling", "a magical barrier",
"a cold breeze", "a whispering voice", "a flowing liquid", "a treasure hoard", "a worn-down banner", "an endless tunnel",
"a hidden door", "a room of statues", "a spiral staircase", "an oversized urn", "an old journal", "a sinister portrait",
"a distorted light", "a large mirror", "a glowing runestone", "an ancient tapestry", "a locked chest", "an intricate lock",
"a set of old footprints", "an iron grate", "a wind-whipped flag", "a lost footstep", "an unfamiliar statue", "a broken door",
"a glowing puddle", "a chained prisoner", "a secret passage", "a forgotten shrine", "a dimly-lit room", "a cursed mark",
"a phantom presence", "an overgrown corridor", "a lost key", "a stench of decay", "an old well", "a strange breeze", "a set of bones"
];
const sensoryDetails = [
"the sound of dripping water", "a faint, musty smell", "a distant echo of footsteps",
"a chilling breeze", "an overwhelming silence", "the flicker of dim light",
"the sound of dripping water", "a faint, musty smell", "a distant echo of footsteps", "a chilling breeze", "an overwhelming silence",
"the flicker of dim light", "a strange hum", "the stench of decay", "the clang of metal", "the rustling of unseen movement",
"a distant, muffled scream", "a faint vibration in the air", "the scrape of stone on stone", "a low, growling noise",
"the crackle of unseen fire", "the scent of burning incense", "the clinking of chains", "a sudden, eerie whisper",
"the flickering of ethereal light", "a hollow, mournful wind", "the churning of water", "a low moan", "the creaking of wood",
"the rustle of wings", "the sharp scent of iron", "the sound of distant chanting", "the warm touch of the air",
"the crackle of energy", "the distant howl of wolves", "the sound of crackling stone", "the echo of heavy breathing",
"the tickling of strange fumes", "a sudden gust of wind", "a quiet pitter-patter", "the sharp crack of a whip",
"the scraping of claws on stone", "the distant rattle of chains", "the smell of burning flesh", "the scent of old books",
"the hum of machinery", "the flutter of curtains", "the soft murmur of voices", "the taste of metal in the air", "the feel of dust on skin",
"a strong earthy odor", "the smell of moss", "the whiff of fresh-brewed tea", "the rush of wind", "the faint metallic taste",
"a distant screeching noise", "the sharp clink of coins", "the warmth of a hearth", "the scent of salt air", "a sudden jolt of light",
"the shrill sound of a whistle", "the sound of stone grinding", "the drip of water onto a stone floor", "the scrape of leather",
"the swish of a cloak", "the scent of damp earth", "a faint rhythmic thumping", "the faint crackle of static",
"the thud of distant footsteps", "the hum of an approaching figure", "the snapping of twigs", "the smell of rotten wood",
"the sound of deep breathing", "the soft rustle of fabric", "the echo of shifting stone", "the distant roar of an animal",
"the low rumble of thunder", "the sound of a creaking gate", "the sharp cry of an animal", "the scent of burning herbs",
"the ringing of distant bells", "the distant roar of a fire", "the sizzle of something burning", "the crackle of dry leaves",
"the scent of wildflowers", "the sound of distant drums", "the faint sound of water running", "the rustle of distant foliage",
"the faint sound of wind in the trees", "the scent of wet earth", "the high-pitched sound of a whistle", "the rumble of an earthquake"
];
// Roll a d20 for each component
const adjective = roomAdjectives[Math.floor(Math.random() * 20)];
const type = roomTypes[Math.floor(Math.random() * 20)];
const feature = notableFeatures[Math.floor(Math.random() * 20)];
const detail = sensoryDetails[Math.floor(Math.random() * 20)];
// Construct the room description
const roomDescription = `You enter a ${adjective.toLowerCase()} ${type.toLowerCase()}, marked by ${feature}. You notice ${detail}.`;
// Display the result to the chat
ChatMessage.create({
content: `<p><strong>Dungeon Room Description:</strong></p><p>${roomDescription}</p>`,
whisper: ChatMessage.getWhisperRecipients("GM") // Replace "GM" with specific users if needed
});
Quest Generation
This macro will generate some quest ideas. Some are a bit weird, but it’s probably good enough to get your brain working on something that makes sense.
// Quest Macro for Foundry VTT with Expanded Variety
const objectives = [
"Rescue", "Retrieve", "Defeat", "Escort", "Investigate", "Protect", "Deliver",
"Destroy", "Recover", "Locate", "Sabotage", "Scout", "Negotiate with", "Guard",
"Capture", "Spy on", "Track down", "Convince", "Uncover", "Seal", "Expel",
"Assist", "Challenge", "Prevent", "Free", "Discover", "Hunt", "Aid",
"Infiltrate", "Tame", "Reclaim", "Appease", "Inspect", "Cleanse", "Subdue",
"Exorcise", "Appeal to", "Test", "Reinforce", "Rebuild", "Prepare", "Rebel against",
"Forge an alliance with", "Defend against", "Escape from", "Find and return",
"Harvest", "Dig up", "Capture and bring back", "Negotiate peace with",
"Smuggle", "Erect defenses for", "Lead an expedition to", "Search for clues about",
"Translate", "Break the curse on", "Map out", "Provide aid to", "Extract secrets from",
"Sabotage the plans of", "Win favor with", "Decipher the runes of", "Warn",
"Bind the power of", "Expose the betrayal of", "Broker a deal with", "Neutralize",
"Meditate between", "Scout the defenses of", "Steal the secrets of", "Protect the journey of",
"Find the heirloom of", "Avenge the wrongs done by", "Defend the honor of", "Bless",
"Curse", "Make contact with", "Silence", "Intimidate", "Escort safely",
"Recover the remains of", "Offer tribute to", "Seek justice for", "Broker a truce with",
"Heal the wounded of", "Unveil the mystery of", "Witness the event of", "Fortify the stronghold of",
"Overthrow the tyranny of", "Steal the treasures of", "Gather intelligence about",
"Create a distraction for", "Forge documents for"
];
const targets = [
"the merchant", "the artifact", "the lost child", "the sacred scroll",
"the captured knight", "the magical gem", "the cursed relic", "the stolen treasure",
"the spy", "the ancient tome", "the rogue mage", "the missing caravan",
"the bandit leader", "the dragon egg", "the elder's staff", "the lost village",
"the royal heir", "the stolen horse", "the treasure map", "the enchanted mirror",
"the rival faction", "the king's crown", "the assassin", "the secret document",
"the rival guild", "the giant's club", "the mysterious orb", "the exiled prince",
"the queen's ring", "the escaped prisoner", "the smuggler's loot", "the alchemist",
"the cursed idol", "the bard's songbook", "the necromancer's grimoire", "the ancient artifact",
"the wandering healer", "the forest spirit", "the unicorn", "the sea serpent",
"the outlaw", "the corrupted cleric", "the undead horde", "the sacred chalice",
"the royal decree", "the spy's notes", "the enemy's battle plans", "the lost explorer",
"the desert hermit", "the shapeshifter", "the ancient oracle", "the dwarven king",
"the elf queen", "the dragon's hoard", "the demon prince", "the celestial shard",
"the goblin shaman", "the haunted armor", "the phoenix egg", "the serpent's fang",
"the pirate king", "the witch's broom", "the golem heart", "the shadow beast",
"the cursed village", "the frost giant", "the volcano god", "the spectral knight",
"the time traveler", "the ruined city", "the ethereal plane", "the desert rose",
"the cloud castle", "the crystal cavern", "the sky pirate", "the arcane librarian"
];
const locations = [
"from the Goblin lair", "in the haunted forest", "within the ruined castle",
"beneath the dwarven mines", "at the abandoned temple", "near the cursed swamp",
"inside the dragon's cave", "on the pirate's island", "in the shadowy crypt",
"hidden in the wizard's tower", "within the frozen tundra", "in the labyrinthine caves",
"under the watchful eye of the mountain giant", "at the forgotten battlefield",
"within the king's dungeons", "from the mercenary camp", "deep in the enchanted grove",
"along the treacherous cliffs", "atop the crumbling watchtower",
"near the forbidden ruins", "inside the fiery volcano", "at the sacred glade",
"beneath the ocean's depths", "within the starry observatory",
"among the nomadic encampment", "on the lonely desert plateau",
"within the eerie graveyard", "on the celestial island", "deep in the jungle temple",
"near the mystical monolith", "inside the royal treasury", "among the spectral ruins",
"in the sunken city", "on the cursed shipwreck", "inside the sky temple",
"at the edge of the world", "within the endless desert", "near the lightning spire",
"beneath the rainbow bridge", "within the hall of echoes", "among the shattered mountains",
"inside the eternal flame", "on the clouded summit", "under the sea of stars",
"in the sacred sanctuary", "at the planar rift", "within the timeless forest",
"beneath the arcane sanctum", "at the dragon's perch", "within the frostbound keep",
"inside the realm of shadows", "in the spectral mist", "on the floating isles",
"beneath the blood moon", "in the abyssal depths", "inside the gilded labyrinth"
];
// Generate a random quest
function generateQuest() {
const objective = objectives[Math.floor(Math.random() * objectives.length)];
const target = targets[Math.floor(Math.random() * targets.length)];
const location = locations[Math.floor(Math.random() * locations.length)];
return `${objective} ${target} ${location}`;
}
// Generate multiple quests
const numberOfQuests = 5; // Adjust the number of quests here
let quests = "";
for (let i = 0; i < numberOfQuests; i++) {
quests += `<li>${generateQuest()}</li>`;
}
// Output to chat
ChatMessage.create({
content: `<h2>Generated Quests</h2><ul>${quests}</ul>`,
whisper: ChatMessage.getWhisperRecipients("GM") // Whisper to the GM
});
The Name Generator
If you are like me, you are terrible at coming up with names. Clicking this macro brings up the following window, which will output ten names for each type. There are exotic names that are good for elves and foreign or exotic human cultures, dwarven names, European-style ones that are more Anglo-Saxon, and Edwardian names.
The drop-down allows you to select between the four name types. And it outputs them as follows:
Some names are funky, but again, this is an inspirational tool. Edit the names to suit your needs.
// Define all name generators
const generators = {
"Exotic": {
prefixes: ["Da", "Gu", "Ir", "Ka", "Lo", "Mi", "Na", "Or", "Pa", "Qu", "Ra", "Sa", "Ta", "Ul", "Va", "Xa", "Za", "El", "Fe", "Go", "He", "Jo", "Ko", "Lu", "Mo", "Ni", "Pi", "Ri", "So", "To", "Vo", "We", "Xe", "Ye", "Zo", "An", "Ba", "Ce", "De", "Eo", "Fi", "Gi", "Hi", "Ja", "Ki", "Li", "Ma", "No", "Oc", "Pe", "Qi", "Re", "Si", "Ti", "Ui", "Wi", "Xe", "Ye", "Ze", "Ar", "Br", "Cr", "Dr", "Er", "Fr", "Gr", "Hr", "Ir", "Jr", "Kr", "Lr", "Mr", "Nr", "Or", "Pr", "Qr", "Rr", "Sr", "Tr", "Ur", "Vr", "Wr", "Xr", "Yr", "Zr"],
infixes: ["ba", "da", "ga", "ha", "ka", "la", "ma", "na", "pa", "ra", "sa", "ta", "va", "xa", "za", "el", "an", "ar", "or", "un", "ir", "io", "is", "il", "os", "us", "am", "im", "em", "um", "ea", "ai", "oi", "ui", "ae", "ee", "ie", "oe", "ue", "al", "el", "il", "ol", "ul", "es", "is", "os", "us", "er", "ir", "or", "ur", "as", "es", "is", "os", "us", "et", "it", "ot", "ut", "en", "in", "on", "un", "em", "im", "om", "um"],
suffixes: ["ird", "ron", "dar", "fal", "gar", "har", "kar", "lar", "mar", "nar", "par", "rar", "sar", "tar", "var", "xar", "zar", "dor", "for", "gor", "hor", "jor", "kor", "lor", "mor", "nor", "por", "ror", "sor", "tor", "vor", "wor", "xor", "yor", "zor", "dan", "fan", "gan", "han", "jan", "kan", "lan", "man", "nan", "pan", "ran", "san", "tan", "van", "wan", "xan", "yan", "zan", "den", "fen", "gen", "hen", "jen", "ken", "len", "men", "nen", "pen", "ren", "sen", "ten", "ven", "wen", "xen", "yen", "zen"],
generate: function() {
return this.prefixes[Math.floor(Math.random() * this.prefixes.length)] +
this.infixes[Math.floor(Math.random() * this.infixes.length)] +
this.suffixes[Math.floor(Math.random() * this.suffixes.length)];
}
},
"European Style": {
prefixes: ["Ae", "Beo", "Ceol", "Dun", "Eal", "Fa", "God", "Ha", "Ing", "Leof", "Mor", "Os", "Raed", "Sig", "Theod", "Wil", "Wyn", "Aethel", "Cyn", "Hild", "Ead", "Ald", "Gyth", "Here", "Frith", "Sael", "Thegn", "Thor", "Tost"],
infixes: ["beor", "gar", "helm", "wine", "wald", "stan", "ric", "mund", "red", "hard", "weald", "sige", "here", "oth", "frith", "berht", "wyne", "heah", "stan", "run", "ward", "wyth", "gyth", "wyn"],
suffixes: ["wulf", "ward", "stan", "ric", "wine", "mund", "helm", "gar", "heah", "wynn", "heard", "forth", "stan", "wyne", "here", "gyth", "ing", "wald", "grim", "oth", "sige", "frith", "berht", "gyrd", "run", "stan", "wielm"],
generate: function() {
return this.prefixes[Math.floor(Math.random() * this.prefixes.length)] +
this.infixes[Math.floor(Math.random() * this.infixes.length)] +
this.suffixes[Math.floor(Math.random() * this.suffixes.length)];
}
},
"Dwarven": {
prefixes: ["Balin", "Durin", "Gloin", "Thrain", "Thorin", "Dain", "Kili", "Fili", "Orin", "Grim", "Rurik", "Barin", "Kaz", "Tor", "Dwalin", "Bofur", "Bombur", "Fundin", "Brokk", "Skor", "Vorn", "Thror", "Farin", "Nain", "Bran", "Bol", "Krag", "Drom", "Varn", "Harn", "Grund"],
suffixes: ["son", "dottir", "var", "rik", "din", "grim", "norn", "gul", "thul", "dag", "ron", "rok", "lok", "bor", "din", "zum", "gron", "mol", "fist", "helm", "stone", "iron", "beard", "breaker", "smith", "forge", "delver", "carver", "ward", "brow", "shield", "ar", "in", "un", "or", "an", "on", "ir", "ur", "ag", "uz", "ok", "im", "om", "orn", "dag", "rak", "gor", "gul", "thol", "mar", "bel", "vor", "grim", "thar", "bryn", "kar", "zan", "dur", "bol"],
generate: function() {
return this.prefixes[Math.floor(Math.random() * this.prefixes.length)] +
this.suffixes[Math.floor(Math.random() * this.suffixes.length)];
}
},
"Edwardian": {
maleFirstNames: ["Arthur", "Albert", "Charles", "Edward", "George", "Henry", "James", "John", "Percy", "Reginald", "Robert", "Thomas", "William", "Frederick", "Francis", "Hugh", "Herbert", "Edmund", "Ernest", "Cecil", "Claude", "Victor", "Leonard", "Sidney", "Harold", "Stanley", "Gilbert", "Maurice", "Walter", "Frank", "Alfred", "Horace", "Clive", "Douglas", "Hubert", "Wilfred", "Oscar", "Ralph", "Archibald", "Bernard"],
femaleFirstNames: ["Alice", "Beatrice", "Clara", "Daisy", "Edith", "Eleanor", "Elizabeth", "Emily", "Florence", "Gertrude", "Grace", "Hannah", "Harriet", "Isabella", "Ivy", "Jane", "Lillian", "Lucy", "Margaret", "Mary", "Maud", "Nellie", "Rose", "Sarah", "Sophia", "Violet", "Winifred", "Agnes", "Catherine", "Dorothy", "Ethel", "Frances", "Georgina", "Helena", "Jessie", "Louisa", "Mabel", "Millicent", "Nora", "Theresa"],
surnames: ["Abbott", "Andrews", "Bailey", "Barker", "Barnes", "Bennett", "Brown", "Carter", "Clark", "Cole", "Collins", "Cooper", "Davies", "Dawson", "Edwards", "Ellis", "Evans", "Foster", "Gibson", "Graham", "Gray", "Green", "Griffiths", "Harris", "Harrison", "Hill", "Hughes", "Jackson", "James", "Johnson", "Jones", "Kelly", "King", "Lewis", "Martin", "Mitchell", "Moore", "Morgan", "Morris", "Murphy", "Parker", "Phillips", "Powell", "Reed", "Reynolds", "Roberts", "Robertson", "Scott", "Smith", "Taylor", "Thomas", "Thompson", "Turner", "Walker", "Ward", "Watson", "White", "Williams", "Wilson", "Wright"],
generate: function() {
const gender = Math.random() < 0.5 ? "male" : "female";
const firstName = gender === "male"
? this.maleFirstNames[Math.floor(Math.random() * this.maleFirstNames.length)]
: this.femaleFirstNames[Math.floor(Math.random() * this.femaleFirstNames.length)];
const surname = this.surnames[Math.floor(Math.random() * this.surnames.length)];
return `${firstName} ${surname}`;
}
}
};
// Create a dialog for selecting the name generator
new Dialog({
title: "Name Generator",
content: `
<form>
<div class="form-group">
<label for="generator">Choose Name Generator:</label>
<select id="generator" name="generator">
<option value="Exotic">Exotic</option>
<option value="European Style">European Style</option>
<option value="Dwarven">Dwarven</option>
<option value="Edwardian">Edwardian</option>
</select>
</div>
</form>
`,
buttons: {
generate: {
icon: '<i class="fas fa-check"></i>',
label: "Generate Names",
callback: html => {
const generatorType = html.find('[name="generator"]').val();
const generator = generators[generatorType];
const numberOfNames = 10; // You can adjust this number if needed
const names = Array.from({length: numberOfNames}, () => generator.generate());
ChatMessage.create({
content: `<h2>${generatorType} Name Generator</h2><ul>${names.map(name => `<li>${name}</li>`).join("")}</ul>`,
whisper: ChatMessage.getWhisperRecipients("GM")
});
}
}
}
}).render(true);
Dungeon Traps
The final macro I’ll share makes simple traps. Click the button, and it outputs the trap to chat as follows:
The randomization is a bit bonkers with some traps it produces, but you have to reinterpret it to make sense. For instance, the trap above triggers via a swinging log; perhaps moving the log out of the way in a passage triggers the net to fall. Or remove the log. Or click the button again until you see something that makes sense.
// Define arrays of trap components
const triggers = [
"Pressure plate", "Tripwire", "Hidden lever", "Loose stone", "Falling object",
"Magical rune", "Illusionary floor", "Hidden button", "Rusty chain", "Rotting rope",
"Suspended weight", "False door", "Unstable ceiling", "Suspicious chest", "Fake keyhole",
"Hanging bell", "Hidden pit", "Swinging log", "Collapsing ladder", "Weak wooden plank",
"Sticky floor", "Sliding wall", "Pivoting statue", "Exploding candle", "Rusty hinge",
"Secret panel", "Unseen tripwire", "Mimic object", "Tension wire", "Falling cage"
];
const effects = [
"Spikes shoot up from the floor (1d6 damage)", "Poison darts fire from the walls (save vs poison)",
"Pit opens beneath the player (2d6 falling damage)", "Ceiling collapses (2d8 damage, area of effect)",
"Gas fills the room (save vs poison or sleep)", "Flame jet bursts out (1d8 damage)",
"Summons a monster (roll on wandering monster table)", "Locks the door behind the party",
"Releases a swarm of insects", "Drops a net on the player",
"Launches a blade pendulum (1d10 damage)", "Sprays acid (1d6 damage, destroys gear)",
"Curses the victim (save or suffer -2 to all rolls for 1d4 hours)",
"Triggers an alarm (alerting nearby enemies)", "Releases a cloud of spores (save vs poison)",
"Reverses gravity (save or take 1d6 damage)", "Freezing blast (1d8 cold damage)", "Exploding floor tiles (2d6 damage)",
"Releases a burst of confounding light (blinded for 1d4 rounds)", "Illusion of safety triggers panic",
"Floor becomes electrified (1d8 damage)", "Wall-mounted blades swing out (1d6 damage)", "Creates a magical barrier",
"Dispels all magic items temporarily", "Floods the room with water", "Triggers a falling rock trap (1d10 damage)",
"Illusion traps characters in endless hallway", "Releases a pack of ravenous rats", "Sprays sticky webbing"
];
// Function to generate a random trap
function generateTrap() {
const trigger = triggers[Math.floor(Math.random() * triggers.length)];
const effect = effects[Math.floor(Math.random() * effects.length)];
return `<b>Trigger:</b> ${trigger}<br><b>Effect:</b> ${effect}`;
}
// Number of traps to generate
const numberOfTraps = 5;
// Generate the traps
const traps = [];
for (let i = 0; i < numberOfTraps; i++) {
traps.push(generateTrap());
}
// Output the traps to chat
ChatMessage.create({
content: `<h2>B/X Trap Generator</h2><ul>${traps.map(trap => `<li>${trap}</li>`).join("")}</ul>`,
whisper: ChatMessage.getWhisperRecipients("GM") // Whisper to GM
});
I remade the tables from the Appendix A generator in 1e's tables section for a dungeon generator. I prefer to use those rather than a macro. Appendix A is quite complex, so making it work in a macro is probably not ideal. I do not have a macro for an Oracle, though I may transfer one from the table I recreated in Foundry into a macro.
For now, this is plenty of valuable macros that can make managing solo gaming and zero-prep gaming easier. However, I find VTTs are often more cumbersome if you try to use them as if you were playing at a real table. And considering AI made these, it seemed only fair to share them for others to use. I then tried to find cool icons with Foundry to place them as icons for the generators.
This way, I can visually pick the macro I need to use. Anyways, enjoy the macros and happy gaming!
If you enjoy this kind of content, share it! Support here on Substack, my Youtube Channel, SubscribeStar, or my Guilded Server.
Check out my books on DriveThruRPG as well or on my Webstore.