initial commit
This commit is contained in:
1406
generators/2024_dnd_treasure/data.js
Normal file
1406
generators/2024_dnd_treasure/data.js
Normal file
File diff suppressed because it is too large
Load Diff
231
generators/2024_dnd_treasure/index.html
Normal file
231
generators/2024_dnd_treasure/index.html
Normal file
@@ -0,0 +1,231 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
padding: 10px;
|
||||
}
|
||||
button, select {
|
||||
font-size: 18px;
|
||||
min-height: 44px;
|
||||
padding: 12px 16px;
|
||||
touch-action: manipulation;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
<title>D&D 2024 Treasure Generator</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>D&D 2024 Treasure Generator</h1>
|
||||
|
||||
<select id="tier">
|
||||
<option value="tier1">1st–4th Level</option>
|
||||
<option value="tier2">5th–10th Level</option>
|
||||
<option value="tier3">11th–16th Level</option>
|
||||
<option value="tier4">17th+ Level</option>
|
||||
</select>
|
||||
<br><br><button onclick="generate()">Generate</button>
|
||||
|
||||
<div id="output"></div>
|
||||
|
||||
<!-- Load external data file -->
|
||||
<script src="data.js"></script>
|
||||
|
||||
<script>
|
||||
function parseInput(text) {
|
||||
const lines = text.trim().split('\n');
|
||||
const result = {};
|
||||
let currentKey = null;
|
||||
for (let line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
// Check if line starts with whitespace (either spaces or tabs)
|
||||
if (!line.match(/^[\s]/)) {
|
||||
currentKey = line.trim();
|
||||
result[currentKey] = [];
|
||||
} else if (currentKey) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Check for weight marker at the end (content ^54)
|
||||
const match = trimmed.match(/^(.*?)\s+\^(\d+)$/);
|
||||
if (match) {
|
||||
const weight = parseInt(match[2]);
|
||||
for (let i = 0; i < weight; i++) result[currentKey].push(match[1]);
|
||||
} else {
|
||||
result[currentKey].push(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function pick(list) {
|
||||
return list[Math.floor(Math.random() * list.length)];
|
||||
}
|
||||
|
||||
function addCommasToNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
function fillTemplate(template, data) {
|
||||
// Handle multiple items with ranges like [[1-4]]x {any_key} FIRST
|
||||
template = template.replace(/\[\[(\d+)-(\d+)\]\]x\s*\{([^}]+)\}/g, (match, min, max, key) => {
|
||||
const count = Math.floor(Math.random() * (parseInt(max) - parseInt(min) + 1)) + parseInt(min);
|
||||
const items = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
let item = pick(data[key] || ['']);
|
||||
|
||||
// Recursively process the item to handle nested substitutions
|
||||
item = fillTemplate(item, data);
|
||||
|
||||
// Only add GP values for art objects (keys that start with "art_")
|
||||
if (key.startsWith('art_')) {
|
||||
const keyParts = key.split('_');
|
||||
if (keyParts.length > 1 && !isNaN(keyParts[keyParts.length - 1])) {
|
||||
const numericValue = keyParts[keyParts.length - 1];
|
||||
const formattedValue = ` (${addCommasToNumber(numericValue)} GP)`;
|
||||
item = `${item}${formattedValue}`;
|
||||
}
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
return items.join('<br>');
|
||||
});
|
||||
|
||||
// Handle patterns like [[8-12]] × 10 GP {gem_10} - note the Unicode multiplication symbol
|
||||
template = template.replace(/\[\[(\d+)-(\d+)\]\]\s*×\s*(\d+)\s*GP\s*\{(gem_\d+)\}/g, (match, min, max, gpValue, key) => {
|
||||
const count = Math.floor(Math.random() * (parseInt(max) - parseInt(min) + 1)) + parseInt(min);
|
||||
const formattedGP = addCommasToNumber(gpValue);
|
||||
const gem = pick(data[key] || ['']);
|
||||
return `${count} × ${formattedGP} GP ${gem}`;
|
||||
});
|
||||
|
||||
// Handle patterns like [[8-12]] × 250 GP gold bars (no gem reference)
|
||||
template = template.replace(/\[\[(\d+)-(\d+)\]\]\s*×\s*(\d+)\s*GP\s*([^{]*?)(?=<br>|$)/g, (match, min, max, gpValue, item) => {
|
||||
const count = Math.floor(Math.random() * (parseInt(max) - parseInt(min) + 1)) + parseInt(min);
|
||||
const formattedGP = addCommasToNumber(gpValue);
|
||||
return `${count} × ${formattedGP} GP ${item.trim()}`;
|
||||
});
|
||||
|
||||
// Handle regular number ranges like [[50-150]]
|
||||
template = template.replace(/\[\[(\d+)-(\d+)\]\]/g, (match, min, max) => {
|
||||
const value = Math.floor(Math.random() * (parseInt(max) - parseInt(min) + 1)) + parseInt(min);
|
||||
return addCommasToNumber(value);
|
||||
});
|
||||
|
||||
// Handle pipe-separated options with mixed braces like {{option1}|{option2}|{option3}}
|
||||
let processedTemplate = template;
|
||||
let startIndex = 0;
|
||||
|
||||
while (true) {
|
||||
const openIndex = processedTemplate.indexOf('{{', startIndex);
|
||||
if (openIndex === -1) break;
|
||||
|
||||
// Find the matching closing }}
|
||||
let braceCount = 0;
|
||||
let closeIndex = -1;
|
||||
for (let i = openIndex + 2; i < processedTemplate.length - 1; i++) {
|
||||
if (processedTemplate.substr(i, 2) === '{{') {
|
||||
braceCount++;
|
||||
i++; // Skip next character
|
||||
} else if (processedTemplate.substr(i, 2) === '}}') {
|
||||
if (braceCount === 0) {
|
||||
closeIndex = i;
|
||||
break;
|
||||
} else {
|
||||
braceCount--;
|
||||
i++; // Skip next character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closeIndex === -1) break;
|
||||
|
||||
const fullMatch = processedTemplate.substring(openIndex, closeIndex + 2);
|
||||
const content = processedTemplate.substring(openIndex + 2, closeIndex);
|
||||
|
||||
// Split on | and clean up each option
|
||||
const options = content.split('|').map(option => {
|
||||
// Remove surrounding braces completely
|
||||
return option.replace(/^\{/, '').replace(/\}$/, '').trim();
|
||||
}).filter(option => option !== '');
|
||||
|
||||
const selectedOption = pick(options);
|
||||
|
||||
// The selected option should be a key name, so look it up in data
|
||||
const result = pick(data[selectedOption] || [selectedOption]);
|
||||
|
||||
// Replace the match with the result
|
||||
processedTemplate = processedTemplate.substring(0, openIndex) + result + processedTemplate.substring(closeIndex + 2);
|
||||
startIndex = openIndex + result.length;
|
||||
}
|
||||
|
||||
template = processedTemplate;
|
||||
|
||||
// Handle single item references like {gem_10} and {art_XXX}
|
||||
// Also handle pipe-separated options in single braces like {option1|option2|option3}
|
||||
template = template.replace(/\{([^}]+)\}/g, (match, content) => {
|
||||
// Check if it contains pipes (multiple options)
|
||||
if (content.includes('|')) {
|
||||
const options = content.split('|').map(option => option.trim()).filter(option => option !== '');
|
||||
const selectedOption = pick(options);
|
||||
const result = pick(data[selectedOption] || [selectedOption]);
|
||||
return result;
|
||||
} else {
|
||||
// Single key lookup
|
||||
const item = pick(data[content] || ['']);
|
||||
return item;
|
||||
}
|
||||
});
|
||||
return template;
|
||||
}
|
||||
|
||||
function generate() {
|
||||
const tier = document.getElementById('tier').value;
|
||||
const parsed = parseInput(dataText); // dataText comes from data.js
|
||||
const output = document.getElementById('output');
|
||||
|
||||
// Start with a random template
|
||||
let result = pick(parsed[tier]);
|
||||
|
||||
// Keep processing until no more substitutions can be made
|
||||
let maxIterations = 10; // Prevent infinite loops
|
||||
let iterations = 0;
|
||||
let previousResult = '';
|
||||
|
||||
while (result !== previousResult && iterations < maxIterations) {
|
||||
previousResult = result;
|
||||
result = fillTemplate(result, parsed);
|
||||
iterations++;
|
||||
}
|
||||
|
||||
// Split into items and filter out empty ones
|
||||
const items = result.split('<br>').filter(item => item.trim() !== '');
|
||||
|
||||
// Capitalize the first character of each bullet point
|
||||
const formattedItems = items.map(item => {
|
||||
const trimmed = item.trim();
|
||||
if (trimmed) {
|
||||
return '- ' + trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
||||
}
|
||||
return '';
|
||||
}).filter(item => item !== '');
|
||||
|
||||
output.innerHTML = '<p>' + formattedItems.join('<br>') + '</p>';
|
||||
}
|
||||
|
||||
// Load data when page loads
|
||||
window.onload = () => generate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
5688
generators/2024_dnd_treasure/index.old
Normal file
5688
generators/2024_dnd_treasure/index.old
Normal file
File diff suppressed because one or more lines are too long
120
generators/2024_dnd_treasure/test_data.js
Normal file
120
generators/2024_dnd_treasure/test_data.js
Normal file
@@ -0,0 +1,120 @@
|
||||
const dataText = `
|
||||
tier1
|
||||
{simple_treasure}<br>[[0-2]]x {basic_items}
|
||||
|
||||
tier2
|
||||
{medium_treasure}<br>[[1-3]]x {good_items}
|
||||
|
||||
simple_treasure
|
||||
[[50-150]] CP, [[10-30]] SP, [[5-25]] GP
|
||||
[[20-80]] CP, [[5-15]] SP<br>[[2-4]] × 10 GP {cheap_gems}
|
||||
[[30-100]] CP<br>[[1-2]]x {art_25}
|
||||
|
||||
medium_treasure
|
||||
[[500-1500]] GP, [[50-150]] PP
|
||||
[[800-1200]] GP<br>[[3-5]] × 50 GP {nice_gems}
|
||||
[[600-1000]] GP<br>[[1-2]]x {art_250}
|
||||
|
||||
cheap_gems
|
||||
Blue stones (pretty blue)
|
||||
Red stones (bright red)
|
||||
Green stones (forest green)
|
||||
|
||||
nice_gems
|
||||
Sapphires (deep blue)
|
||||
Rubies (crimson red)
|
||||
Emeralds (bright green)
|
||||
|
||||
art_25
|
||||
{condition} {race} silver cup
|
||||
{condition} {race} carved statue
|
||||
{condition} {race} gold ring
|
||||
|
||||
art_250
|
||||
{condition} {race} jeweled crown
|
||||
{condition} {race} silk robe
|
||||
{condition} {race} crystal orb
|
||||
|
||||
basic_items
|
||||
{simple_weapons} ^60
|
||||
{simple_armor} ^30
|
||||
{utility_items} ^10
|
||||
|
||||
good_items
|
||||
{magic_weapons} ^50
|
||||
{magic_armor} ^30
|
||||
{rare_items} ^20
|
||||
|
||||
simple_weapons
|
||||
Iron Sword
|
||||
Wooden Staff
|
||||
Steel Dagger
|
||||
|
||||
simple_armor
|
||||
Leather Armor
|
||||
Chain Mail
|
||||
Iron Shield
|
||||
|
||||
magic_weapons
|
||||
{magical_properties} {weapon_types}
|
||||
{magical_properties} {weapon_types}
|
||||
|
||||
magic_armor
|
||||
{magical_properties} {armor_types}
|
||||
|
||||
weapon_types
|
||||
Sword
|
||||
Axe
|
||||
Bow
|
||||
Staff
|
||||
|
||||
armor_types
|
||||
Leather Armor
|
||||
Chain Mail
|
||||
Plate Armor
|
||||
|
||||
magical_properties
|
||||
+1
|
||||
Flaming
|
||||
Frost
|
||||
Lightning
|
||||
|
||||
utility_items
|
||||
Healing Potion
|
||||
Rope (50 feet)
|
||||
Lantern
|
||||
|
||||
rare_items
|
||||
{{wizard_spells}|{cleric_spells}|{special_items}}
|
||||
Crystal of {{fire}|{ice}|{lightning}} Power
|
||||
Scroll of {wizard_spells}
|
||||
|
||||
wizard_spells
|
||||
Fireball
|
||||
Magic Missile
|
||||
Teleport
|
||||
|
||||
cleric_spells
|
||||
Heal
|
||||
Bless
|
||||
Turn Undead
|
||||
|
||||
special_items
|
||||
Bag of Holding
|
||||
Ring of Invisibility
|
||||
Cloak of Flying
|
||||
|
||||
condition
|
||||
Ornate
|
||||
Ancient
|
||||
Masterwork
|
||||
Crude
|
||||
Elegant
|
||||
|
||||
race
|
||||
Elven
|
||||
Dwarven
|
||||
Human
|
||||
Orcish
|
||||
Dragon
|
||||
`;
|
||||
Reference in New Issue
Block a user