The Appendix A Dungeon Generator as a Foundry VTT Macro
Appendix A Dungeon Generation with Just a Few Clicks!
In this previous post, I talked about macros for Foundry VTT. In that post,
I explain how the code can be pasted into your macros for use in your games.
I am no coder, so there could be problems with this macro, but I asked Grok to code a macro that would replicate the Appendix A Dungeon Generator from Advanced Dungeons and Dragons 1e. And it did it! It does not produce the entrance room as an image, but it does tell you which entrance to pick (entrances 1, 2, 3, etc.).
When the macro is clicked, a window in Foundry opens, and you can select from all the tables in Appendix A from the drop-down menu.
If you click roll on the menu, it spits out the random result from the appropriate table in chat as follows:
This is a much quicker way to use Appendix A in my online games. Next week, I will post a video on my YouTube channel discussing a cool add-on called Dungeon Draw. This macro, combined with that tool, means instant zero-prep dungeons in your Foundry games.
Here is the code. Again, AI made it, but it seems to work fine and output all of the same results in Appendix A to your chat. And since I didn’t make it, it seems like for the greater good, it should be free. Because this is pretty cool. It does whisper it to the GM (so you) despite the above images not showing that. I fixed that part. Here is the code:
const tables = {
"TABLE ENTRANCE": {
dieSize: 20,
results: [
{ range: [1, 4], result: "Entrance 1"},
{ range: [5, 8], result: "Entrance 2"},
{ range: [9, 12], result: "Entrance 3"},
{ range: [13, 16], result: "Entrance 4"},
{ range: [17, 20], result: "Entrance 5"}
]
},
"TABLE I - Periodic Check": {
dieSize: 20,
results: [
{ range: [1, 2], result: "Continue straight - check again in 60'" },
{ range: [3, 5], result: "Door (see TABLE II)" },
{ range: [6, 10], result: "Side Passage (see TABLE III) - check again in 30'" },
{ range: [11, 13], result: "Passage Turns (see TABLE IV, check width on TABLE III)" },
{ range: [14, 16], result: "Chamber (see TABLE V) - check 30' after leaving" },
{ range: [17, 17], result: "Stairs (see TABLE VI)" },
{ range: [18, 18], result: "Dead End" },
{ range: [19, 19], result: "Trick/trap (see TABLE VII), passage continues - check again in 30'" },
{ range: [20, 20], result: "Wandering Monster, check again immediately" }
]
},
"TABLE II - Doors": {
dieSize: 20,
results: [
{ range: [1, 6], result: "Left" },
{ range: [7, 12], result: "Right" },
{ range: [13, 20], result: "Ahead" }
],
additional: [
{ range: [1, 4], result: "Parallel passage, or 10' x 10' room if door is straight ahead" },
{ range: [5, 8], result: "Passage straight ahead" },
{ range: [9, 9], result: "Passage 45 degrees ahead/behind" },
{ range: [10, 10], result: "Passage 45 degrees behind/ahead" },
{ range: [11, 18], result: "Room (go to TABLE V)" },
{ range: [19, 20], result: "Chamber (go to TABLE V)" }
]
},
"TABLE III - Side Passages": {
dieSize: 20,
results: [
{ range: [1, 2], result: "left 90 degrees" },
{ range: [3, 4], result: "right 90 degrees" },
{ range: [5, 5], result: "left 45 degrees ahead" },
{ range: [6, 6], result: "right 45 degrees ahead" },
{ range: [7, 7], result: "left 45 degrees behind (left 135 degrees)" },
{ range: [8, 8], result: "right 45 degrees behind (right 135 degrees)" },
{ range: [9, 9], result: "left curve 45 degrees ahead" },
{ range: [10, 10], result: "right curve 45 degrees ahead" },
{ range: [11, 13], result: "passage \"T\"s" },
{ range: [14, 15], result: "passage \"Y\"s" },
{ range: [16, 19], result: "four-way intersection" },
{ range: [20, 20], result: "passage \"X\"s" }
]
},
"TABLE III.A - Passage Width": {
dieSize: 20,
results: [
{ range: [1, 12], result: "10'" },
{ range: [13, 16], result: "20'" },
{ range: [17, 17], result: "30'" },
{ range: [18, 18], result: "5'" },
{ range: [19, 20], result: "SPECIAL PASSAGE (TABLE III.B)" }
]
},
"TABLE III.B - Special Passage": {
dieSize: 20,
results: [
{ range: [1, 4], result: "40', columns down center" },
{ range: [5, 7], result: "40', double row of columns" },
{ range: [8, 10], result: "50', double row of columns" },
{ range: [11, 12], result: "50', columns 10' right and left support 10' wide upper galleries" },
{ range: [13, 15], result: "10' stream" },
{ range: [16, 17], result: "20' river" },
{ range: [18, 18], result: "40' river" },
{ range: [19, 19], result: "60' river" },
{ range: [20, 20], result: "20', chasm" }
]
},
"TABLE IV - Turns": {
dieSize: 20,
results: [
{ range: [1, 8], result: "left 90 degrees" },
{ range: [9, 9], result: "left 45 degrees ahead" },
{ range: [10, 10], result: "left 45 degrees behind (left 135 degrees)" },
{ range: [11, 18], result: "right 90 degrees" },
{ range: [19, 19], result: "right 45 degrees ahead" },
{ range: [20, 20], result: "right 45 degrees behind (right 135 degrees)" }
]
},
"TABLE V - Chambers and Rooms Shape and Size": {
dieSize: 20,
results: [
{ range: [1, 2], result: "Square, 20' x 20'" },
{ range: [3, 4], result: "Square, 30' x 30'" },
{ range: [5, 6], result: "Square, 40' x 40'" },
{ range: [7, 8], result: "Rectangular, 20' x 30'" },
{ range: [9, 10], result: "Rectangular, 30' x 50'" },
{ range: [11, 13], result: "Rectangular, 40' x 60'" },
{ range: [14, 15], result: "Unusual shape and size (see TABLE V.A and V.B)" },
{ range: [16, 20], result: "Unusual shape and size (see TABLE V.A and V.B)" }
]
},
"TABLE V.A - Unusual Shape": {
dieSize: 20,
results: [
{ range: [1, 5], result: "Circular" },
{ range: [6, 8], result: "Triangular" },
{ range: [9, 11], result: "Trapezoidal" },
{ range: [12, 13], result: "Odd-shaped" },
{ range: [14, 15], result: "Oval" },
{ range: [16, 17], result: "Hexagonal" },
{ range: [18, 19], result: "Octagonal" },
{ range: [20, 20], result: "Cave" }
]
},
"TABLE V.B - Unusual Size": {
dieSize: 20,
results: [
{ range: [1, 3], result: "about 500 sq. ft." },
{ range: [4, 6], result: "about 900 sq. ft." },
{ range: [7, 8], result: "about 1,300 sq. ft." },
{ range: [9, 10], result: "about 2,000 sq. ft." },
{ range: [11, 12], result: "about 2,700 sq. ft." },
{ range: [13, 14], result: "about 3,400 sq. ft." },
{ range: [15, 20], result: "Roll again and add to 2,000 sq. ft." }
]
},
"TABLE V.C - Number of Exits": {
dieSize: 20,
results: [
{ range: [1, 3], result: "1 Exit", condition: "over 600'" },
{ range: [4, 6], result: "2 Exits", condition: "over 600'" },
{ range: [7, 9], result: "3 Exits", condition: "over 1200'" },
{ range: [10, 12], result: "4 Exits", condition: "over 1600'" },
{ range: [13, 15], result: "0 Exits*", condition: "any size" },
{ range: [16, 18], result: "1 Exit", condition: "up to 600'" },
{ range: [19, 19], result: "1-4 Exits (d4)", condition: "up to 1200'" },
{ range: [20, 20], result: "1 door in chamber, passage in room", condition: "up to 1600'" }
]
},
"TABLE V.D - Exit Locations": {
dieSize: 20,
results: [
{ range: [1, 7], result: "opposite wall" },
{ range: [8, 12], result: "left wall" },
{ range: [13, 17], result: "right wall" },
{ range: [18, 20], result: "same wall" }
],
footnote: "If the space beyond the wall is already mapped, the exit is either a secret door (1-5), a one-way door (6-10), or in the opposite direction (11-20)."
},
"TABLE V.E - Exit Direction": {
dieSize: 20,
results: [
{ range: [1, 16], result: "straight ahead" },
{ range: [17, 18], result: "45 degrees left/right" },
{ range: [19, 20], result: "45 degrees right/left" }
],
note: "If a Door, use TABLE II; check width on TABLE III.A."
},
"TABLE V.F - Chamber or Room Contents": {
dieSize: 20,
results: [
{ range: [1, 12], result: "Empty" },
{ range: [13, 14], result: "Monster only" },
{ range: [15, 17], result: "Monster and treasure" },
{ range: [18, 18], result: "Special or contains stairway" },
{ range: [19, 19], result: "Trick/trap" },
{ range: [20, 20], result: "Treasure" }
],
specialNote: "Special includes: stairway up 1 level (1-5), up 2 levels (6-8), down 1 level (9-14), down 2 levels (15-19), or down 3 levels with 2 flights of stairs and a slanting passageway (20)."
},
"TABLE V.G - Treasure": {
dieSize: 100,
results: [
{ range: [1, 25], result: "1,000 copper pieces/level", withMonster: "Take two rolls on 'Without Monster'" },
{ range: [26, 50], result: "1,000 silver pieces/level", withMonster: "Take two rolls on 'Without Monster'" },
{ range: [51, 65], result: "750 electrum pieces/level", withMonster: "Take two rolls on 'Without Monster'" },
{ range: [66, 80], result: "250 gold pieces/level", withMonster: "Take two rolls on 'Without Monster'" },
{ range: [81, 90], result: "100 platinum pieces/level", withMonster: "Take two rolls on 'Without Monster'" },
{ range: [91, 94], result: "1-4 gems/level", withMonster: "Take two rolls on 'Without Monster'" },
{ range: [95, 97], result: "1 piece of jewelry/level", withMonster: "Take two rolls on 'Without Monster'" },
{ range: [98, 100], result: "Magic (roll once on Magic Items Table)", withMonster: "Add 10% to the total of each roll" }
]
},
"TABLE V.H - Treasure is Contained In": {
dieSize: 20,
results: [
{ range: [1, 2], result: "Bags" },
{ range: [3, 4], result: "Sacks" },
{ range: [5, 6], result: "Small Coffers" },
{ range: [7, 8], result: "Chests" },
{ range: [9, 10], result: "Huge Chests" },
{ range: [11, 12], result: "Pottery Jars" },
{ range: [13, 14], result: "Metal Urns" },
{ range: [15, 16], result: "Stone Containers" },
{ range: [17, 18], result: "Iron Trunks" },
{ range: [19, 20], result: "Loose" }
],
note: "Roll on TABLE V.I for 1-8, TABLE V.J for 9-20 for protection if desired."
},
"TABLE V.I - Treasure is Guarded By": {
dieSize: 20,
results: [
{ range: [1, 2], result: "Contact poison on container" },
{ range: [3, 4], result: "Contact poison on treasure" },
{ range: [5, 6], result: "Poisoned needles in lock" },
{ range: [7, 7], result: "Poisoned needles in handles" },
{ range: [8, 8], result: "Spring darts firing from front of container" },
{ range: [9, 9], result: "Spring darts firing up from top of container" },
{ range: [10, 10], result: "Spring darts firing up from inside bottom of container" },
{ range: [11, 12], result: "Blade scything across inside" },
{ range: [13, 13], result: "Poisonous insects or reptiles living inside container" },
{ range: [14, 14], result: "Gas released by opening container" },
{ range: [15, 15], result: "Trapdoor opening in front of container" },
{ range: [16, 16], result: "Trapdoor opening 6' in front of container" },
{ range: [17, 17], result: "Stone block dropping in front of the container" },
{ range: [18, 18], result: "Spears released from walls when container opened" },
{ range: [19, 19], result: "Explosive runes" },
{ range: [20, 20], result: "Symbol" }
]
},
"TABLE V.J - Treasure is Hidden By/In": {
dieSize: 20,
results: [
{ range: [1, 3], result: "Invisibility" },
{ range: [4, 5], result: "Illusion (to change or hide appearance)" },
{ range: [6, 6], result: "Secret space under container" },
{ range: [7, 8], result: "Secret compartment in container" },
{ range: [9, 9], result: "Inside ordinary item in plain view" },
{ range: [10, 10], result: "Disguised to appear as something else" },
{ range: [11, 11], result: "Under a heap of trash/dung" },
{ range: [12, 13], result: "Under a loose stone in the floor" },
{ range: [14, 15], result: "Behind a loose stone in the wall" },
{ range: [16, 20], result: "In a secret room nearby" }
]
},
"TABLE VI - Stairs": {
dieSize: 20,
results: [
{ range: [1, 5], result: "Down 1 level", note: "* 1 in 20 has a door which closes egress for the day." },
{ range: [6, 6], result: "Down 2 levels", note: "** 2 in 20 has a door which closes egress for the day." },
{ range: [7, 7], result: "Down 3 levels", note: "*** 3 in 20 has a door which closes egress for the day." },
{ range: [8, 8], result: "Up 1 level" },
{ range: [9, 9], result: "Up dead end (1 in 6 chance to chute down 2 levels)" },
{ range: [10, 10], result: "Down dead end (1 in 6 chance to chute down 1 level)" },
{ range: [11, 11], result: "Chimney up 1 level, passage continues, check again in 30'" },
{ range: [12, 12], result: "Chimney up 2 levels, passage continues, check again in 30'" },
{ range: [13, 13], result: "Chimney down 2 levels, passage continues, check again in 30'" },
{ range: [14, 16], result: "Trap door down 1 level, passage continues, check again in 30'" },
{ range: [17, 17], result: "Trap door down 2 levels, passage continues, check again in 30'" },
{ range: [18, 20], result: "Up 1 then down 2 (total down 1), chamber at end (roll on TABLE V)" }
]
},
"TABLE VII - Trick/Trap": {
dieSize: 20,
results: [
{ range: [1, 5], result: "Secret Door (Non-elf 3 in 20, Elf 10 in 20, Magical device 18 in 20)" },
{ range: [6, 7], result: "Pit, 10' deep, 3 in 6 to fall in" },
{ range: [8, 8], result: "Pit, 10' deep with spikes, 3 in 6 to fall in" },
{ range: [9, 9], result: "20' x 20' elevator room, descends 1 level for 30 turns" },
{ range: [10, 10], result: "Elevator room descends 2 levels for 30 turns" },
{ range: [11, 11], result: "Elevator room descends 2-5 levels, 1 level upon entering, additional levels on door opening attempts" },
{ range: [12, 12], result: "Wall slides across passage for 40-60 turns" },
{ range: [13, 13], result: "Oil and cinder trap (2-12 h.p. damage unless save vs. magic for 1-3 h.p.)" },
{ range: [14, 14], result: "Crushing pit, 10' deep, 3 in 6 to fall in, walls move together in 2-5 rounds" },
{ range: [15, 15], result: "Arrow trap, 1-3 arrows, 1 in 20 is poisoned" },
{ range: [16, 16], result: "Spear trap, 1-3 spears, 1 in 20 is poisoned" },
{ range: [17, 17], result: "Gas trap (see TABLE VII.A)" },
{ range: [18, 18], result: "Falling door or stone, causing damage (1-10 or 2-20 hit points)" },
{ range: [19, 19], result: "Illusionary wall hiding another trap or chamber" },
{ range: [20, 20], result: "Chute down 1 level (unascendable)" }
]
},
"TABLE VII.A - Gas Sub-Table": {
dieSize: 20,
results: [
{ range: [1, 7], result: "Only obscures vision" },
{ range: [8, 9], result: "Blinds for 1-6 turns" },
{ range: [10, 12], result: "Fear (run back 120' unless saving throw vs. magic)" },
{ range: [13, 13], result: "Sleep for 2-12 turns" },
{ range: [14, 18], result: "Strength boost for fighters for 1-10 hours" },
{ range: [19, 19], result: "Sickness (return to surface immediately)" },
{ range: [20, 20], result: "Poison (dead unless save vs. poison)" }
]
},
"TABLE VIII - Caves and Caverns": {
dieSize: 20,
results: [
{ range: [1, 5], result: "Cave about 40' x 60'" },
{ range: [6, 7], result: "Cave about 50' x 75'" },
{ range: [8, 9], result: "Double Cave: 20' x 30', 60' x 60'" },
{ range: [10, 11], result: "Double Cave: 35' x 50', 80' x 90'" },
{ range: [12, 14], result: "Cavern about 95' x 125'" },
{ range: [15, 16], result: "Cavern about 120' x 150'" },
{ range: [17, 18], result: "Cavern about 150' x 200'" },
{ range: [19, 20], result: "Mammoth cavern about 250'-300' x 350'-400'" }
],
note: "Roll for pool or lake if indicated."
},
"TABLE VIII.A - Pools": {
dieSize: 20,
results: [
{ range: [1, 8], result: "No pool" },
{ range: [9, 10], result: "Pool, no monster" },
{ range: [11, 12], result: "Pool, monster" },
{ range: [13, 18], result: "Pool, monster & treasure" },
{ range: [19, 20], result: "Magical pool" }
]
},
"TABLE VIII.B - Lakes": {
dieSize: 20,
results: [
{ range: [1, 10], result: "No lake" },
{ range: [11, 15], result: "Lake, no monsters" },
{ range: [16, 18], result: "Lake, monsters" },
{ range: [19, 20], result: "Enchanted lake" }
]
},
"TABLE VIII.C - Magic Pools": {
dieSize: 20,
results: [
{ range: [1, 8], result: "Turns gold to platinum or lead, one time only" },
{ range: [9, 15], result: "Adds or subtracts from one characteristic" },
{ range: [16, 17], result: "Talking pool grants wish or damages" },
{ range: [18, 20], result: "Transporter pool" }
]
}
};
function rollDie(dieSize) {
return Math.floor(Math.random() * dieSize) + 1;
}
function getResult(tableName, roll) {
const table = tables[tableName];
if (!table) return "Error: Table not found";
for (let entry of table.results) {
if (roll >= entry.range[0] && roll <= entry.range[1]) {
let result = entry.result;
if (tableName === "TABLE II" && table.additional) {
let additionalRoll = rollDie(20);
for (let add of table.additional) {
if (additionalRoll >= add.range[0] && additionalRoll <= add.range[1]) {
result += ` - ${add.result}`;
break;
}
}
}
if (tableName === "TABLE V.F" && entry.specialNote) {
result += ` - ${entry.specialNote}`;
}
if (tableName === "TABLE V.G" && entry.withMonster) {
result += ` (With Monster: ${entry.withMonster})`;
}
if (table.note) result += ` - ${table.note}`;
if (table.footnote) result += ` - ${table.footnote}`;
return result;
}
}
return "Error: Roll not within range";
}
function generateDungeon(selectedTable) {
let roll = rollDie(tables[selectedTable].dieSize);
let result = getResult(selectedTable, roll);
ChatMessage.create({
content: `Rolling on ${selectedTable}: <br>${result}`,
whisper: [game.user.id] // This should be changed to the GM's user ID or use the GM role
});
}
// UI to select which table to roll on
new Dialog({
title: "Dungeon Generation",
content: `
<form>
<div class="form-group">
<label>Select Table:</label>
<select id="tableSelect" name="table">
${Object.keys(tables).map(table => `<option value="${table}">${table}</option>`).join('')}
</select>
</div>
</form>
`,
buttons: {
roll: {
icon: '<i class="fas fa-dice-d20"></i>',
label: "Roll",
callback: (html) => {
let selectedTable = html.find('#tableSelect')[0].value;
generateDungeon(selectedTable);
}
}
}
}).render(true);
I hope this macro helps you out with your VTT zero prep games!
Someone get this to Mr. Wargaming: Not Jon Mollison, STAT!