Merge branch 'upstream' into dev-submaps

This commit is contained in:
GoteGuru 2022-03-30 19:42:51 +00:00
commit 9b618544e6
8 changed files with 379 additions and 397 deletions

View file

@ -50,6 +50,7 @@ iframe {
mask-mode: alpha; mask-mode: alpha;
mask-clip: no-clip; mask-clip: no-clip;
fill-rule: evenodd; fill-rule: evenodd;
user-select: none;
} }
#canvas { #canvas {

View file

@ -959,6 +959,7 @@
<option value="isthmus">Isthmus</option> <option value="isthmus">Isthmus</option>
<option value="shattered">Shattered</option> <option value="shattered">Shattered</option>
<option value="taklamakan">Taklamakan</option> <option value="taklamakan">Taklamakan</option>
<option value="oldWorld">Old World</option>
</optgroup> </optgroup>
<optgroup label="Specific"> <optgroup label="Specific">
<option value="africa-centric">Africa Centric</option> <option value="africa-centric">Africa Centric</option>
@ -2404,6 +2405,7 @@
<option value="isthmus">Isthmus</option> <option value="isthmus">Isthmus</option>
<option value="shattered">Shattered</option> <option value="shattered">Shattered</option>
<option value="taklamakan">Taklamakan</option> <option value="taklamakan">Taklamakan</option>
<option value="oldWorld">Old World</option>
</select> </select>
</div> </div>
<div id="templateTools"> <div id="templateTools">

View file

@ -119,5 +119,56 @@ window.HeightmapTemplates = (function () {
Hill 3-4 60-85 20-80 0-5 Hill 3-4 60-85 20-80 0-5
Hill 3-4 60-85 20-80 95-100`; Hill 3-4 60-85 20-80 95-100`;
return {volcano, highIsland, lowIsland, continents, archipelago, atoll, mediterranean, peninsula, peninsula, pangea, isthmus, shattered, taklamakan};
const oldWorld = `Hill 4-6 20-40 15-85 30-45
Hill 3-7 20-40 15-85 55-70
Strait 2-7 vertical 0 0
Pit 1-2 40-50 35-55 20-80
Strait 2-7 vertical 0 0
Range 2-3 20-25 15-35 20-30
Range 2-3 20-25 15-35 65-80
Range 2-3 20-25 45-85 20-45
Range 2-3 20-25 45-85 65-80
Multiply .9 80-100 0 0
Strait 2-7 vertical 0 0
Pit 2-3 40-50 45-65 20-80
Trough 1-2 40-50 15-45 20-45
Trough 1-3 40-50 15-45 45-80
Trough 1-2 40-50 45-85 20-45
Trough 1-2 40-50 45-85 45-80
Multiply 1.2 17-20 0 0
Strait 2-7 horizontal 0 0
Multiply 1.2 17-50 0 0
Range 1-2 20-25 15-45 45-65
Range 1-2 20-25 65-85 45-80
Multiply 1.1 50-80 0 0
Hill 1-2 20 15-45 20-80
Hill 1-2 20 65-85 20-80
Multiply 1.2 15-30 0 0
Strait 2-7 vertical 0 0
Trough 1-2 40-50 35-65 65-80
Range 1-2 20-25 15-35 20-45
Strait 2-7 vertical 0 0
Range 1-2 20-25 65-85 45-80
Multiply .9 70-100 0 0
Hill 1-2 20-25 15-45 65-80
Hill 1-2 20-25 65-85 20-45
Hill 1 20-25 15-45 45-65
Hill 1 20-25 65-85 45-65
Strait 2-7 vertical 0 0
Trough 1-2 20-50 15-45 45-65
Trough 1-2 20-50 65-85 45-65
Strait 2-7 horizontal 0 0
Multiply 0.8 70-100 0 0
Hill 1-2 20-25 35-45 45-65
Hill 1-2 20-25 65-70 45-65
Pit 2-3 40-50 45-65 30-70
Trough 1-2 40-50 15-85 65-80
Trough 1-2 40-50 15-85 10-35
Strait 2-5 vertical 0 0
Multiply 1.1 45-90 0 0
Strait 3-7 vertical 0 0
Trough 1-2 40-50 45-65 45-65`;
return {volcano, highIsland, lowIsland, continents, archipelago, atoll, mediterranean, peninsula, peninsula, pangea, isthmus, shattered, taklamakan, oldWorld};
})(); })();

View file

@ -9,27 +9,27 @@ window.Markers = (function () {
const isFantasy = culturesSet.includes("Fantasy"); const isFantasy = culturesSet.includes("Fantasy");
return [ return [
{type: "volcanoes", icon: "🌋", multiplier: 1, fn: addVolcanoes}, {type: "volcanoes", icon: "🌋", dx: 52, px: 13, min: 10, each: 500, multiplier: 1, list: listVolcanoes, add: addVolcano},
{type: "hot-springs", icon: "♨️", multiplier: 1, fn: addHotSprings}, {type: "hot-springs", icon: "♨️", dy: 52, min: 30, each: 1200, multiplier: 1, list: listHotSprings, add: addHotSpring},
{type: "mines", icon: "⛏️", multiplier: 1, fn: addMines}, {type: "mines", icon: "⛏️", dx: 48, px: 13, min: 1, each: 15, multiplier: 1, list: listMines, add: addMine},
{type: "bridges", icon: "🌉", multiplier: 1, fn: addBridges}, {type: "bridges", icon: "🌉", px: 14, min: 1, each: 5, multiplier: 1, list: listBridges, add: addBridge},
{type: "inns", icon: "🍻", multiplier: 1, fn: addInns}, {type: "inns", icon: "🍻", px: 14, min: 1, each: 100, multiplier: 1, list: listInns, add: addInn},
{type: "lighthouses", icon: "🚨", multiplier: 1, fn: addLighthouses}, {type: "lighthouses", icon: "🚨", px: 14, min: 1, each: 2, multiplier: 1, list: listLighthouses, add: addLighthouse},
{type: "waterfalls", icon: "⟱", multiplier: 1, fn: addWaterfalls}, {type: "waterfalls", icon: "⟱", dy: 54, px: 16, min: 1, each: 5, multiplier: 1, list: listWaterfalls, add: addWaterfall},
{type: "battlefields", icon: "⚔️", multiplier: 1, fn: addBattlefields}, {type: "battlefields", icon: "⚔️", dy: 52, min: 50, each: 700, multiplier: 1, list: listBattlefields, add: addBattlefield},
{type: "dungeons", icon: "🗝️", multiplier: 1, fn: addDungeons}, {type: "dungeons", icon: "🗝️", dy: 51, px: 13, min: 30, each: 200, multiplier: 1, list: listDungeons, add: addDungeon},
{type: "lake-monsters", icon: "🐉", multiplier: 1, fn: addLakeMonsters}, {type: "lake-monsters", icon: "🐉", dy: 48, min: 2, each: 10, multiplier: 1, list: listLakeMonsters, add: addLakeMonster},
{type: "sea-monsters", icon: "🦑", multiplier: 1, fn: addSeaMonsters}, {type: "sea-monsters", icon: "🦑", min: 50, each: 700, multiplier: 1, list: listSeaMonsters, add: addSeaMonster},
{type: "hill-monsters", icon: "👹", multiplier: 1, fn: addHillMonsters}, {type: "hill-monsters", icon: "👹", dy: 54, px: 13, min: 30, each: 600, multiplier: 1, list: listHillMonsters, add: addHillMonster},
{type: "sacred-mountains", icon: "🗻", multiplier: 1, fn: addSacredMountains}, {type: "sacred-mountains", icon: "🗻", dy: 48, min: 1, each: 5, multiplier: 1, list: listSacredMountains, add: addSacredMountain},
{type: "sacred-forests", icon: "🌳", multiplier: 1, fn: addSacredForests}, {type: "sacred-forests", icon: "🌳", min: 30, each: 1000, multiplier: 1, list: listSacredForests, add: addSacredForest},
{type: "sacred-pineries", icon: "🌲", multiplier: 1, fn: addSacredPineries}, {type: "sacred-pineries", icon: "🌲", px: 13, min: 30, each: 800, multiplier: 1, list: listSacredPineries, add: addSacredPinery},
{type: "sacred-palm-groves", icon: "🌴", multiplier: 1, fn: addSacredPalmGroves}, {type: "sacred-palm-groves", icon: "🌴", px: 13, min: 1, each: 100, multiplier: 1, list: listSacredPalmGroves, add: addSacredPalmGrove},
{type: "brigands", icon: "💰", multiplier: 1, fn: addBrigands}, {type: "brigands", icon: "💰", px: 13, min: 50, each: 100, multiplier: 1, list: listBrigands, add: addBrigands},
{type: "pirates", icon: "🏴‍☠️", multiplier: 1, fn: addPirates}, {type: "pirates", icon: "🏴‍☠️", dx: 51, min: 40, each: 300, multiplier: 1, list: listPirates, add: addPirates},
{type: "statues", icon: "🗿", multiplier: 1, fn: addStatues}, {type: "statues", icon: "🗿", min: 80, each: 1200, multiplier: 1, list: listStatues, add: addStatue},
{type: "ruines", icon: "🏺", multiplier: 1, fn: addRuines}, {type: "ruines", icon: "🏺", min: 80, each: 1200, multiplier: 1, list: listRuins, add: addRuins},
{type: "portals", icon: "🌀", multiplier: +isFantasy, fn: addPortals} {type: "portals", icon: "🌀", px: 14, min: 16, each: 8, multiplier: +isFantasy, list: listPortals, add: addPortal}
]; ];
} }
@ -61,12 +61,36 @@ window.Markers = (function () {
generateTypes(); generateTypes();
}; };
const add = marker => {
const base = config.find(c => c.type === marker.type);
if (base) {
const {icon, type, dx, dy, px} = base;
marker = addMarker({icon, type, dx, dy, px}, marker);
base.add("marker" + marker.i, marker.cell);
return marker;
}
const i = last(pack.markers)?.i + 1 || 0;
pack.markers.push({...marker, i});
occupied[marker.cell] = true;
return {...marker, i};
};
function generateTypes() { function generateTypes() {
TIME && console.time("addMarkers"); TIME && console.time("addMarkers");
config.forEach(({type, icon, multiplier, fn}) => { config.forEach(({type, icon, dx, dy, px, min, each, multiplier, list, add}) => {
if (multiplier === 0) return; if (multiplier === 0) return;
fn(type, icon, multiplier);
let candidates = Array.from(list(pack));
let quantity = getQuantity(candidates, min, each, multiplier);
while (quantity && candidates.length) {
const [cell] = extractAnyElement(candidates);
const marker = addMarker({icon, type, dx, dy, px}, {cell});
add("marker" + marker.i, cell);
quantity--;
}
}); });
occupied = []; occupied = [];
@ -96,87 +120,65 @@ window.Markers = (function () {
return cells.p[cell]; return cells.p[cell];
} }
function addMarker({cell, type, icon, dx, dy, px}) { function addMarker(base, marker) {
const i = last(pack.markers)?.i + 1 || 0; const i = last(pack.markers)?.i + 1 || 0;
const [x, y] = getMarkerCoordinates(cell); const [x, y] = getMarkerCoordinates(marker.cell);
const marker = {i, icon, type, x, y, cell}; marker = {...base, x, y, ...marker, i};
if (dx) marker.dx = dx;
if (dy) marker.dy = dy;
if (px) marker.px = px;
pack.markers.push(marker); pack.markers.push(marker);
occupied[cell] = true; occupied[marker.cell] = true;
return "marker" + i; return marker;
} }
function addVolcanoes(type, icon, multiplier) { function listVolcanoes({cells}) {
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70);
}
function addVolcano(id, cell) {
const {cells} = pack; const {cells} = pack;
let mountains = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 70).sort((a, b) => cells.h[b] - cells.h[a]));
let quantity = getQuantity(mountains, 10, 500, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(mountains);
const id = addMarker({cell, icon, type, dx: 52, px: 13});
const proper = Names.getCulture(cells.culture[cell]); const proper = Names.getCulture(cells.culture[cell]);
const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper; const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper;
notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight(cells.p[cell])}`}); notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight(cells.p[cell])}`});
quantity--;
}
} }
function addHotSprings(type, icon, multiplier) { function listHotSprings({cells}) {
return cells.i.filter(i => !occupied[i] && cells.h[i] > 50);
}
function addHotSpring(id, cell) {
const {cells} = pack; const {cells} = pack;
let springs = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] > 50).sort((a, b) => cells.h[b] - cells.h[a]));
let quantity = getQuantity(springs, 30, 1200, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(springs);
const id = addMarker({cell, icon, type, dy: 52});
const proper = Names.getCulture(cells.culture[cell]); const proper = Names.getCulture(cells.culture[cell]);
const temp = convertTemperature(gauss(35, 15, 20, 100)); const temp = convertTemperature(gauss(35, 15, 20, 100));
notes.push({id, name: proper + " Hot Springs", legend: `A hot springs area. Average temperature: ${temp}`}); notes.push({id, name: proper + " Hot Springs", legend: `A hot springs area. Average temperature: ${temp}`});
quantity--;
}
} }
function addMines(type, icon, multiplier) { function listMines({cells}) {
return cells.i.filter(i => !occupied[i] && cells.h[i] > 47 && cells.burg[i]);
}
function addMine(id, cell) {
const {cells} = pack; const {cells} = pack;
let hillyBurgs = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] > 47 && cells.burg[i]));
let quantity = getQuantity(hillyBurgs, 1, 15, multiplier);
if (!quantity) return;
const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1}; const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1};
while (quantity && hillyBurgs.length) {
const [cell] = extractAnyElement(hillyBurgs);
const id = addMarker({cell, icon, type, dx: 48, px: 13});
const resource = rw(resources); const resource = rw(resources);
const burg = pack.burgs[cells.burg[cell]]; const burg = pack.burgs[cells.burg[cell]];
const name = `${burg.name}${resource} mining town`; const name = `${burg.name}${resource} mining town`;
const population = rn(burg.population * populationRate * urbanization); const population = rn(burg.population * populationRate * urbanization);
const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`; const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addBridges(type, icon, multiplier) { function listBridges({cells, burgs}) {
const {cells, burgs} = pack;
const meanFlux = d3.mean(cells.fl.filter(fl => fl)); const meanFlux = d3.mean(cells.fl.filter(fl => fl));
let bridges = Array.from( return cells.i.filter(
cells.i.filter(i => !occupied[i] && cells.burg[i] && cells.t[i] !== 1 && burgs[cells.burg[i]].population > 20 && cells.r[i] && cells.fl[i] > meanFlux) i => !occupied[i] && cells.burg[i] && cells.t[i] !== 1 && burgs[cells.burg[i]].population > 20 && cells.r[i] && cells.fl[i] > meanFlux
); );
let quantity = getQuantity(bridges, 1, 5, multiplier); }
if (!quantity) return;
function addBridge(id, cell) {
const {cells} = pack;
while (quantity) {
const [cell] = extractAnyElement(bridges);
const id = addMarker({cell, icon, type, px: 14});
const burg = pack.burgs[cells.burg[cell]]; const burg = pack.burgs[cells.burg[cell]];
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]); const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
const riverName = river ? `${river.name} ${river.type}` : "river"; const riverName = river ? `${river.name} ${river.type}` : "river";
@ -191,17 +193,13 @@ window.Markers = (function () {
weathered: 1 weathered: 1
}; };
notes.push({id, name: `${name} Bridge`, legend: `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}`}); notes.push({id, name: `${name} Bridge`, legend: `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}`});
quantity--;
}
} }
function addInns(type, icon, multiplier) { function listInns({cells}) {
const {cells} = pack; return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.road[i] > 4 && cells.pop[i] > 10);
}
let taverns = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.road[i] > 4 && cells.pop[i] > 10));
let quantity = getQuantity(taverns, 1, 100, multiplier);
if (!quantity) return;
function addInn(id, cell) {
const colors = [ const colors = [
"Dark", "Dark",
"Light", "Light",
@ -430,45 +428,35 @@ window.Markers = (function () {
"sap" "sap"
]; ];
while (quantity) {
const [cell] = extractAnyElement(taverns);
const id = addMarker({cell, icon, type, px: 14});
const typeName = P(0.3) ? "inn" : "tavern"; const typeName = P(0.3) ? "inn" : "tavern";
const isAnimalThemed = P(0.7); const isAnimalThemed = P(0.7);
const animal = ra(animals); const animal = ra(animals);
const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(type); const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(typeName);
const meal = isAnimalThemed && P(0.3) ? animal : ra(courses); const meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
const course = `${ra(methods)} ${meal}`.toLowerCase(); const course = `${ra(methods)} ${meal}`.toLowerCase();
const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase(); const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase();
const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here`; const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here`;
notes.push({id, name: "The " + name, legend}); notes.push({id, name: "The " + name, legend});
quantity--;
}
} }
function addLighthouses(type, icon, multiplier) { function listLighthouses({cells}) {
return cells.i.filter(i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c]));
}
function addLighthouse(id, cell) {
const {cells} = pack; const {cells} = pack;
const lighthouses = Array.from(cells.i.filter(i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c])));
let quantity = getQuantity(lighthouses, 1, 2, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(lighthouses);
const id = addMarker({cell, icon, type, px: 14});
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to serve as a beacon for ships in the open sea`}); notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to serve as a beacon for ships in the open sea`});
quantity--;
}
} }
function addWaterfalls(type, icon, multiplier) { function listWaterfalls({cells}) {
return cells.i.filter(i => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some(c => cells.h[c] < 40 && cells.r[c]));
}
function addWaterfall(id, cell) {
const {cells} = pack; const {cells} = pack;
const waterfalls = Array.from(cells.i.filter(i => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some(c => cells.h[c] < 40 && cells.r[c])));
const quantity = getQuantity(waterfalls, 1, 5, multiplier);
if (!quantity) return;
const descriptions = [ const descriptions = [
"A gorgeous waterfall flows here", "A gorgeous waterfall flows here",
"The rapids of an exceptionally beautiful waterfall", "The rapids of an exceptionally beautiful waterfall",
@ -477,24 +465,18 @@ window.Markers = (function () {
"A river drops down from a great height forming a wonderous waterfall", "A river drops down from a great height forming a wonderous waterfall",
"A breathtaking waterfall cuts through the landscape" "A breathtaking waterfall cuts through the landscape"
]; ];
for (let i = 0; i < waterfalls.length && i < quantity; i++) {
const cell = waterfalls[i];
const id = addMarker({cell, icon, type, dy: 54, px: 16});
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend: `${ra(descriptions)}`}); notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend: `${ra(descriptions)}`});
} }
function listBattlefields({cells}) {
return cells.i.filter(i => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25);
} }
function addBattlefields(type, icon, multiplier) { function addBattlefield(id, cell) {
const {cells, states} = pack; const {cells, states} = pack;
let battlefields = Array.from(cells.i.filter(i => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25));
let quantity = getQuantity(battlefields, 50, 700, multiplier);
if (!quantity) return;
while (quantity && battlefields.length) {
const [cell] = extractAnyElement(battlefields);
const id = addMarker({cell, icon, type, dy: 52});
const state = states[cells.state[cell]]; const state = states[cells.state[cell]];
if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state); if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
const campaign = ra(state.campaigns); const campaign = ra(state.campaigns);
@ -502,73 +484,56 @@ window.Markers = (function () {
const name = Names.getCulture(cells.culture[cell]) + " Battlefield"; const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`; const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addDungeons(type, icon, multiplier) { function listDungeons({cells}) {
const {cells} = pack; return cells.i.filter(i => !occupied[i] && cells.pop[i] && cells.pop[i] < 3);
}
let dungeons = Array.from(cells.i.filter(i => !occupied[i] && cells.pop[i] && cells.pop[i] < 3));
let quantity = getQuantity(dungeons, 30, 200, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(dungeons);
const id = addMarker({cell, icon, type, dy: 51, px: 13});
function addDungeon(id, cell) {
const dungeonSeed = `${seed}${cell}`; const dungeonSeed = `${seed}${cell}`;
const name = "Dungeon"; const name = "Dungeon";
const legend = `<div>Undiscovered dungeon. See <a href="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" target="_blank">One page dungeon</a></div><iframe src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`; const legend = `<div>Undiscovered dungeon. See <a href="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" target="_blank">One page dungeon</a></div><iframe src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addLakeMonsters(type, icon, multiplier) { function listLakeMonsters({features}) {
const {features} = pack; return features
.filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell])
.map(feature => feature.firstCell);
}
const lakes = features.filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell]); function addLakeMonster(id, cell) {
let quantity = getQuantity(lakes, 2, 10, multiplier); const lake = pack.features[pack.cells.f[cell]];
if (!quantity) return;
// Check that the feature is a lake in case the user clicked on a wrong
// square
if (lake.type !== "lake") return;
while (quantity) {
const [lake] = extractAnyElement(lakes);
const cell = lake.firstCell;
const id = addMarker({cell, icon, type, dy: 48});
const name = `${lake.name} Monster`; const name = `${lake.name} Monster`;
const length = gauss(10, 5, 5, 100); const length = gauss(10, 5, 5, 100);
const legend = `Rumors say a relic monster of ${length} ${heightUnit.value} long inhabits ${lake.name} Lake. Truth or lie, folks are afraid to fish in the lake`; const legend = `Rumors say a relic monster of ${length} ${heightUnit.value} long inhabits ${lake.name} Lake. Truth or lie, folks are afraid to fish in the lake`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addSeaMonsters(type, icon, multiplier) { function listSeaMonsters({cells, features}) {
const {cells, features} = pack; return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === "ocean");
}
const sea = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === "ocean")); function addSeaMonster(id, cell) {
let quantity = getQuantity(sea, 50, 700, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(sea);
const id = addMarker({cell, icon, type});
const name = `${Names.getCultureShort(0)} Monster`; const name = `${Names.getCultureShort(0)} Monster`;
const length = gauss(25, 10, 10, 100); const length = gauss(25, 10, 10, 100);
const legend = `Old sailors tell stories of a gigantic sea monster inhabiting these dangerous waters. Rumors say it can be ${length} ${heightUnit.value} long`; const legend = `Old sailors tell stories of a gigantic sea monster inhabiting these dangerous waters. Rumors say it can be ${length} ${heightUnit.value} long`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addHillMonsters(type, icon, multiplier) { function listHillMonsters({cells}) {
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
}
function addHillMonster(id, cell) {
const {cells} = pack; const {cells} = pack;
const hills = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]));
let quantity = getQuantity(hills, 30, 600, multiplier);
if (!quantity) return;
const adjectives = [ const adjectives = [
"great", "great",
"big", "big",
@ -619,100 +584,77 @@ window.Markers = (function () {
"attacks unsuspecting victims" "attacks unsuspecting victims"
]; ];
while (quantity) {
const [cell] = extractAnyElement(hills);
const id = addMarker({cell, icon, type, dy: 54, px: 13});
const monster = ra(species); const monster = ra(species);
const toponym = Names.getCulture(cells.culture[cell]); const toponym = Names.getCulture(cells.culture[cell]);
const name = `${toponym} ${monster}`; const name = `${toponym} ${monster}`;
const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`; const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addSacredMountains(type, icon, multiplier) { // Sacred mountains spawn on lonely mountains
function listSacredMountains({cells}) {
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70 && cells.c[i].some(c => cells.culture[c]) && cells.c[i].every(c => cells.h[c] < 60));
}
function addSacredMountain(id, cell) {
const {cells, cultures} = pack; const {cells, cultures} = pack;
let lonelyMountains = Array.from(
cells.i.filter(i => !occupied[i] && cells.h[i] >= 70 && cells.c[i].some(c => cells.culture[c]) && cells.c[i].every(c => cells.h[c] < 60))
);
let quantity = getQuantity(lonelyMountains, 1, 5, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(lonelyMountains);
const id = addMarker({cell, icon, type, dy: 48});
const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c); const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c);
const name = `${Names.getCulture(culture)} Mountain`; const name = `${Names.getCulture(culture)} Mountain`;
const height = getFriendlyHeight(cells.p[cell]); const height = getFriendlyHeight(cells.p[cell]);
const legend = `A sacred mountain of ${cultures[culture].name} culture. Height: ${height}`; const legend = `A sacred mountain of ${cultures[culture].name} culture. Height: ${height}`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addSacredForests(type, icon, multiplier) { // Sacred forests spawn on temperate forests
function listSacredForests({cells}) {
return cells.i.filter(i => !occupied[i] && cells.culture[i] && [6, 8].includes(cells.biome[i]));
}
function addSacredForest(id, cell) {
const {cells, cultures} = pack; const {cells, cultures} = pack;
let temperateForests = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && [6, 8].includes(cells.biome[i])));
let quantity = getQuantity(temperateForests, 30, 1000, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(temperateForests);
const id = addMarker({cell, icon, type});
const culture = cells.culture[cell]; const culture = cells.culture[cell];
const name = `${Names.getCulture(culture)} Forest`; const name = `${Names.getCulture(culture)} Forest`;
const legend = `A sacred forest of ${cultures[culture].name} culture`; const legend = `A sacred forest of ${cultures[culture].name} culture`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addSacredPineries(type, icon, multiplier) { // Sacred pineries spawn on boreal forests
function listSacredPineries({cells}) {
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.biome[i] === 9);
}
function addSacredPinery(id, cell) {
const {cells, cultures} = pack; const {cells, cultures} = pack;
let borealForests = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.biome[i] === 9));
let quantity = getQuantity(borealForests, 30, 800, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(borealForests);
const id = addMarker({cell, icon, type, px: 13});
const culture = cells.culture[cell]; const culture = cells.culture[cell];
const name = `${Names.getCulture(culture)} Pinery`; const name = `${Names.getCulture(culture)} Pinery`;
const legend = `A sacred pinery of ${cultures[culture].name} culture`; const legend = `A sacred pinery of ${cultures[culture].name} culture`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addSacredPalmGroves(type, icon, multiplier) { // Sacred palm groves spawn on oasises
function listSacredPalmGroves({cells}) {
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.biome[i] === 1 && cells.pop[i] > 1 && cells.road[i]);
}
function addSacredPalmGrove(id, cell) {
const {cells, cultures} = pack; const {cells, cultures} = pack;
let oasises = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.biome[i] === 1 && cells.pop[i] > 1 && cells.road[i]));
let quantity = getQuantity(oasises, 1, 100, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(oasises);
const id = addMarker({cell, icon, type, px: 13});
const culture = cells.culture[cell]; const culture = cells.culture[cell];
const name = `${Names.getCulture(culture)} Palm Grove`; const name = `${Names.getCulture(culture)} Palm Grove`;
const legend = `A sacred palm grove of ${cultures[culture].name} culture`; const legend = `A sacred palm grove of ${cultures[culture].name} culture`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addBrigands(type, icon, multiplier) { function listBrigands({cells}) {
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.road[i] > 4);
}
function addBrigands(id, cell) {
const {cells} = pack; const {cells} = pack;
let roads = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.road[i] > 4));
let quantity = getQuantity(roads, 50, 100, multiplier);
if (!quantity) return;
const animals = [ const animals = [
"Apes", "Apes",
"Badgers", "Badgers",
@ -747,9 +689,6 @@ window.Markers = (function () {
]; ];
const types = {brigands: 4, bandits: 3, robbers: 1, highwaymen: 1}; const types = {brigands: 4, bandits: 3, robbers: 1, highwaymen: 1};
while (quantity) {
const [cell] = extractAnyElement(roads);
const id = addMarker({cell, icon, type, px: 13});
const culture = cells.culture[cell]; const culture = cells.culture[cell];
const biome = cells.biome[cell]; const biome = cells.biome[cell];
const height = cells.p[cell]; const height = cells.p[cell];
@ -768,32 +707,25 @@ window.Markers = (function () {
const name = `${Names.getCulture(culture)} ${ra(animals)}`; const name = `${Names.getCulture(culture)} ${ra(animals)}`;
const legend = `A gang of ${locality} ${rw(types)}`; const legend = `A gang of ${locality} ${rw(types)}`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addPirates(type, icon, multiplier) { // Pirates spawn on sea routes
const {cells} = pack; function listPirates({cells}) {
return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i]);
}
let searoutes = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i])); function addPirates(id, cell) {
let quantity = getQuantity(searoutes, 40, 300, multiplier);
if (!quantity) return;
while (quantity) {
const [cell] = extractAnyElement(searoutes);
const id = addMarker({cell, icon, type, dx: 51});
const name = `Pirates`; const name = `Pirates`;
const legend = `Pirate ships have been spotted in these waters`; const legend = `Pirate ships have been spotted in these waters`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addStatues(type, icon, multiplier) { function listStatues({cells}) {
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.h[i] < 40);
}
function addStatue(id, cell) {
const {cells} = pack; const {cells} = pack;
let statues = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.h[i] < 40));
let quantity = getQuantity(statues, 80, 1200, multiplier);
if (!quantity) return;
const variants = ["Statue", "Obelisk", "Monument", "Column", "Monolith", "Pillar", "Megalith", "Stele", "Runestone", "Sculpture", "Effigy", "Idol"]; const variants = ["Statue", "Obelisk", "Monument", "Column", "Monolith", "Pillar", "Megalith", "Stele", "Runestone", "Sculpture", "Effigy", "Idol"];
const scripts = { const scripts = {
@ -804,9 +736,6 @@ window.Markers = (function () {
mongolian: "᠀᠐᠑᠒ᠠᠡᠦᠧᠨᠩᠪᠭᠮᠯᠰᠱᠲᠳᠵᠻᠼᠽᠾᠿᡀᡁᡆᡍᡎᡏᡐᡑᡒᡓᡔᡕᡖᡗᡙᡜᡝᡞᡟᡠᡡᡭᡮᡯᡰᡱᡲᡳᡴᢀᢁᢂᢋᢏᢐᢑᢒᢓᢛᢜᢞᢟᢠᢡᢢᢤᢥᢦ" mongolian: "᠀᠐᠑᠒ᠠᠡᠦᠧᠨᠩᠪᠭᠮᠯᠰᠱᠲᠳᠵᠻᠼᠽᠾᠿᡀᡁᡆᡍᡎᡏᡐᡑᡒᡓᡔᡕᡖᡗᡙᡜᡝᡞᡟᡠᡡᡭᡮᡯᡰᡱᡲᡳᡴᢀᢁᢂᢋᢏᢐᢑᢒᢓᢛᢜᢞᢟᢠᢡᢢᢤᢥᢦ"
}; };
while (quantity) {
const [cell] = extractAnyElement(statues);
const id = addMarker({cell, icon, type});
const culture = cells.culture[cell]; const culture = cells.culture[cell];
const variant = ra(variants); const variant = ra(variants);
@ -819,16 +748,13 @@ window.Markers = (function () {
const legend = `An ancient ${variant.toLowerCase()}. It has an inscription, but no one can translate it: const legend = `An ancient ${variant.toLowerCase()}. It has an inscription, but no one can translate it:
<div style="font-size: 1.8em; line-break: anywhere;">${inscription}</div>`; <div style="font-size: 1.8em; line-break: anywhere;">${inscription}</div>`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addRuines(type, icon, multiplier) { function listRuins({cells}) {
const {cells} = pack; return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60);
let ruins = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60)); }
let quantity = getQuantity(ruins, 80, 1200, multiplier);
if (!quantity) return;
function addRuins(id, cell) {
const types = [ const types = [
"City", "City",
"Town", "Town",
@ -845,37 +771,30 @@ window.Markers = (function () {
"Castle" "Castle"
]; ];
while (quantity) {
const [cell] = extractAnyElement(ruins);
const id = addMarker({cell, icon, type});
const ruinType = ra(types); const ruinType = ra(types);
const name = `Ruined ${ruinType}`; const name = `Ruined ${ruinType}`;
const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`; const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
function addPortals(type, icon, multiplier) { function listPortals({burgs}) {
const {burgs} = pack; return burgs
let portals = burgs
.slice(1, Math.ceil(burgs.length / 10) + 1) .slice(1, Math.ceil(burgs.length / 10) + 1)
.filter(({cell}) => cell && !occupied[cell]) .filter(({cell}) => !occupied[cell])
.map(burg => [burg.name, burg.cell]); .map(burg => burg.cell);
let quantity = getQuantity(portals, 16, 8, multiplier); }
if (!quantity) return;
function addPortal(id, cell) {
const {cells, burgs} = pack;
// Portals can only be added to burgs
if (cells.burg[cell]) return;
const burgName = burgs[cells.burg[cell]].name;
while (quantity) {
const [portal] = extractAnyElement(portals);
const [burgName, cell] = portal;
const id = addMarker({cell, icon, type, px: 14});
const name = `${burgName} Portal`; const name = `${burgName} Portal`;
const legend = `An element of the magic portal system connecting major cities. Portals installed centuries ago, but still work fine`; const legend = `An element of the magic portal system connecting major cities. Portals installed centuries ago, but still work fine`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--;
}
} }
return {generate, regenerate, getConfig, setConfig}; return {add, generate, regenerate, getConfig, setConfig};
})(); })();

View file

@ -1481,6 +1481,12 @@ function drawRivers() {
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => { const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
if (!cells || cells.length < 2) return; if (!cells || cells.length < 2) return;
if (points && points.length !== cells.length) {
console.error(`River ${i} has ${cells.length} cells, but only ${points.length} points defined. Resetting points data`);
points = undefined;
}
const meanderedPoints = addMeandering(cells, points); const meanderedPoints = addMeandering(cells, points);
const path = getRiverPath(meanderedPoints, widthFactor, sourceWidth); const path = getRiverPath(meanderedPoints, widthFactor, sourceWidth);
return `<path id="river${i}" d="${path}"/>`; return `<path id="river${i}" d="${path}"/>`;

View file

@ -86,8 +86,8 @@ function editNotes(id, name) {
selector: "#notesLegend", selector: "#notesLegend",
height: "90%", height: "90%",
menubar: false, menubar: false,
plugins: `autolink lists link charmap print formatpainter casechange code fullscreen image link media table paste hr checklist wordcount`, plugins: `autolink lists link charmap print code fullscreen image link media table paste hr wordcount`,
toolbar: `code | undo redo | bold italic strikethrough | forecolor backcolor | formatpainter removeformat | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media table | fontselect fontsizeselect | blockquote hr casechange checklist charmap | print fullscreen`, toolbar: `code | undo redo | removeformat | bold italic strikethrough | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media table | fontselect fontsizeselect | blockquote hr charmap | print fullscreen`,
media_alt_source: false, media_alt_source: false,
media_poster: false, media_poster: false,
setup: editor => { setup: editor => {

View file

@ -105,7 +105,7 @@ function showSupporters() {
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger,Joshua Xiong,Moist mongol, Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger,Joshua Xiong,Moist mongol,
Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce, Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce,
Jordan,William Markus,Sidr Dim,Alexander Whittaker,The Next Level,Patrick Valverde,Markus Peham,Daniel Cooper,the Beagles of Neorbus,Marley Moule, Jordan,William Markus,Sidr Dim,Alexander Whittaker,The Next Level,Patrick Valverde,Markus Peham,Daniel Cooper,the Beagles of Neorbus,Marley Moule,
Maximilian Schielke,Johnathan Xavier Hutchinson,Ele,Rita`; Maximilian Schielke,Johnathan Xavier Hutchinson,Ele,Rita,Randy Ross,John Wick,RedSpaz,cameron cannon,Ian Grau-Fay,Kyle Barrett,Charlotte Wiland`;
const array = supporters const array = supporters
.replace(/(?:\r\n|\r|\n)/g, "") .replace(/(?:\r\n|\r|\n)/g, "")
@ -579,17 +579,18 @@ function randomizeOptions() {
function randomizeHeightmapTemplate() { function randomizeHeightmapTemplate() {
const templates = { const templates = {
volcano: 3, volcano: 3,
highIsland: 22, highIsland: 19,
lowIsland: 9, lowIsland: 9,
continents: 19, continents: 16,
archipelago: 23, archipelago: 18,
mediterranean: 5, mediterranean: 5,
peninsula: 3, peninsula: 3,
pangea: 5, pangea: 5,
isthmus: 2, isthmus: 2,
atoll: 1, atoll: 1,
shattered: 7, shattered: 7,
taklamakan: 1 taklamakan: 1,
oldWorld: 11
}; };
document.getElementById("templateInput").value = rw(templates); document.getElementById("templateInput").value = rw(templates);
} }

View file

@ -706,14 +706,16 @@ function addMarkerOnClick() {
const point = d3.mouse(this); const point = d3.mouse(this);
const x = rn(point[0], 2); const x = rn(point[0], 2);
const y = rn(point[1], 2); const y = rn(point[1], 2);
const i = markers.length ? last(markers).i + 1 : 0;
// Find the current cell
const cell = findCell(point[0], point[1]);
// Find the currently selected marker to use as a base
const isMarkerSelected = markers.length && elSelected?.node()?.parentElement?.id === "markers"; const isMarkerSelected = markers.length && elSelected?.node()?.parentElement?.id === "markers";
const selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null; const selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null;
const baseMarker = selectedMarker || {icon: "❓"}; const baseMarker = selectedMarker || {icon: "❓"};
const marker = {...baseMarker, i, x, y}; const marker = Markers.add({...baseMarker, x, y, cell});
markers.push(marker);
const markersElement = document.getElementById("markers"); const markersElement = document.getElementById("markers");
const rescale = +markersElement.getAttribute("rescale"); const rescale = +markersElement.getAttribute("rescale");
markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale)); markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale));