diff --git a/modules/heightmap-templates.js b/modules/heightmap-templates.js
index 3704a3e4..bd371e10 100644
--- a/modules/heightmap-templates.js
+++ b/modules/heightmap-templates.js
@@ -119,5 +119,56 @@ window.HeightmapTemplates = (function () {
Hill 3-4 60-85 20-80 0-5
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};
})();
diff --git a/modules/markers-generator.js b/modules/markers-generator.js
index de64195f..6c3e0398 100644
--- a/modules/markers-generator.js
+++ b/modules/markers-generator.js
@@ -9,27 +9,27 @@ window.Markers = (function () {
const isFantasy = culturesSet.includes("Fantasy");
return [
- {type: "volcanoes", icon: "🌋", multiplier: 1, fn: addVolcanoes},
- {type: "hot-springs", icon: "♨️", multiplier: 1, fn: addHotSprings},
- {type: "mines", icon: "⛏️", multiplier: 1, fn: addMines},
- {type: "bridges", icon: "🌉", multiplier: 1, fn: addBridges},
- {type: "inns", icon: "🍻", multiplier: 1, fn: addInns},
- {type: "lighthouses", icon: "🚨", multiplier: 1, fn: addLighthouses},
- {type: "waterfalls", icon: "⟱", multiplier: 1, fn: addWaterfalls},
- {type: "battlefields", icon: "⚔️", multiplier: 1, fn: addBattlefields},
- {type: "dungeons", icon: "🗝️", multiplier: 1, fn: addDungeons},
- {type: "lake-monsters", icon: "🐉", multiplier: 1, fn: addLakeMonsters},
- {type: "sea-monsters", icon: "🦑", multiplier: 1, fn: addSeaMonsters},
- {type: "hill-monsters", icon: "👹", multiplier: 1, fn: addHillMonsters},
- {type: "sacred-mountains", icon: "🗻", multiplier: 1, fn: addSacredMountains},
- {type: "sacred-forests", icon: "🌳", multiplier: 1, fn: addSacredForests},
- {type: "sacred-pineries", icon: "🌲", multiplier: 1, fn: addSacredPineries},
- {type: "sacred-palm-groves", icon: "🌴", multiplier: 1, fn: addSacredPalmGroves},
- {type: "brigands", icon: "💰", multiplier: 1, fn: addBrigands},
- {type: "pirates", icon: "🏴☠️", multiplier: 1, fn: addPirates},
- {type: "statues", icon: "🗿", multiplier: 1, fn: addStatues},
- {type: "ruines", icon: "🏺", multiplier: 1, fn: addRuines},
- {type: "portals", icon: "🌀", multiplier: +isFantasy, fn: addPortals}
+ {type: "volcanoes", icon: "🌋", dx: 52, px: 13, min: 10, each: 500, multiplier: 1, list: listVolcanoes, add: addVolcano},
+ {type: "hot-springs", icon: "♨️", dy: 52, min: 30, each: 1200, multiplier: 1, list: listHotSprings, add: addHotSpring},
+ {type: "mines", icon: "⛏️", dx: 48, px: 13, min: 1, each: 15, multiplier: 1, list: listMines, add: addMine},
+ {type: "bridges", icon: "🌉", px: 14, min: 1, each: 5, multiplier: 1, list: listBridges, add: addBridge},
+ {type: "inns", icon: "🍻", px: 14, min: 1, each: 100, multiplier: 1, list: listInns, add: addInn},
+ {type: "lighthouses", icon: "🚨", px: 14, min: 1, each: 2, multiplier: 1, list: listLighthouses, add: addLighthouse},
+ {type: "waterfalls", icon: "⟱", dy: 54, px: 16, min: 1, each: 5, multiplier: 1, list: listWaterfalls, add: addWaterfall},
+ {type: "battlefields", icon: "⚔️", dy: 52, min: 50, each: 700, multiplier: 1, list: listBattlefields, add: addBattlefield},
+ {type: "dungeons", icon: "🗝️", dy: 51, px: 13, min: 30, each: 200, multiplier: 1, list: listDungeons, add: addDungeon},
+ {type: "lake-monsters", icon: "🐉", dy: 48, min: 2, each: 10, multiplier: 1, list: listLakeMonsters, add: addLakeMonster},
+ {type: "sea-monsters", icon: "🦑", min: 50, each: 700, multiplier: 1, list: listSeaMonsters, add: addSeaMonster},
+ {type: "hill-monsters", icon: "👹", dy: 54, px: 13, min: 30, each: 600, multiplier: 1, list: listHillMonsters, add: addHillMonster},
+ {type: "sacred-mountains", icon: "🗻", dy: 48, min: 1, each: 5, multiplier: 1, list: listSacredMountains, add: addSacredMountain},
+ {type: "sacred-forests", icon: "🌳", min: 30, each: 1000, multiplier: 1, list: listSacredForests, add: addSacredForest},
+ {type: "sacred-pineries", icon: "🌲", px: 13, min: 30, each: 800, multiplier: 1, list: listSacredPineries, add: addSacredPinery},
+ {type: "sacred-palm-groves", icon: "🌴", px: 13, min: 1, each: 100, multiplier: 1, list: listSacredPalmGroves, add: addSacredPalmGrove},
+ {type: "brigands", icon: "💰", px: 13, min: 50, each: 100, multiplier: 1, list: listBrigands, add: addBrigands},
+ {type: "pirates", icon: "🏴☠️", dx: 51, min: 40, each: 300, multiplier: 1, list: listPirates, add: addPirates},
+ {type: "statues", icon: "🗿", min: 80, each: 1200, multiplier: 1, list: listStatues, add: addStatue},
+ {type: "ruines", icon: "🏺", min: 80, each: 1200, multiplier: 1, list: listRuins, add: addRuins},
+ {type: "portals", icon: "🌀", px: 14, min: 16, each: 8, multiplier: +isFantasy, list: listPortals, add: addPortal}
];
}
@@ -61,12 +61,36 @@ window.Markers = (function () {
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() {
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;
- 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 = [];
@@ -96,112 +120,86 @@ window.Markers = (function () {
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 [x, y] = getMarkerCoordinates(cell);
- const marker = {i, icon, type, x, y, cell};
- if (dx) marker.dx = dx;
- if (dy) marker.dy = dy;
- if (px) marker.px = px;
+ const [x, y] = getMarkerCoordinates(marker.cell);
+ marker = {...base, x, y, ...marker, i};
pack.markers.push(marker);
- occupied[cell] = true;
- return "marker" + i;
+ occupied[marker.cell] = true;
+ return marker;
}
- function addVolcanoes(type, icon, multiplier) {
- 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 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])}`});
- quantity--;
- }
+ function listVolcanoes({cells}) {
+ return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70);
}
- function addHotSprings(type, icon, multiplier) {
+ function addVolcano(id, cell) {
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 temp = convertTemperature(gauss(35, 15, 20, 100));
- notes.push({id, name: proper + " Hot Springs", legend: `A hot springs area. Average temperature: ${temp}`});
- quantity--;
- }
+ const proper = Names.getCulture(cells.culture[cell]);
+ 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])}`});
}
- function addMines(type, icon, multiplier) {
+ function listHotSprings({cells}) {
+ return cells.i.filter(i => !occupied[i] && cells.h[i] > 50);
+ }
+
+ function addHotSpring(id, cell) {
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 proper = Names.getCulture(cells.culture[cell]);
+ const temp = convertTemperature(gauss(35, 15, 20, 100));
+ notes.push({id, name: proper + " Hot Springs", legend: `A hot springs area. Average temperature: ${temp}`});
+ }
+
+ 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 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 burg = pack.burgs[cells.burg[cell]];
- const name = `${burg.name} — ${resource} mining town`;
- const population = rn(burg.population * populationRate * urbanization);
- const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const resource = rw(resources);
+ const burg = pack.burgs[cells.burg[cell]];
+ const name = `${burg.name} — ${resource} mining town`;
+ const population = rn(burg.population * populationRate * urbanization);
+ const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
+ notes.push({id, name, legend});
}
- function addBridges(type, icon, multiplier) {
- const {cells, burgs} = pack;
-
+ function listBridges({cells, burgs}) {
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
- let bridges = Array.from(
- 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)
+ return 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
);
- let quantity = getQuantity(bridges, 1, 5, multiplier);
- if (!quantity) return;
-
- while (quantity) {
- const [cell] = extractAnyElement(bridges);
- const id = addMarker({cell, icon, type, px: 14});
- const burg = pack.burgs[cells.burg[cell]];
- const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
- const riverName = river ? `${river.name} ${river.type}` : "river";
- const name = river && P(0.2) ? river.name : burg.name;
- const weightedAdjectives = {
- stone: 10,
- wooden: 1,
- lengthy: 2,
- formidable: 2,
- rickety: 1,
- beaten: 1,
- weathered: 1
- };
- 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 addBridge(id, cell) {
const {cells} = pack;
- 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;
+ const burg = pack.burgs[cells.burg[cell]];
+ const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
+ const riverName = river ? `${river.name} ${river.type}` : "river";
+ const name = river && P(0.2) ? river.name : burg.name;
+ const weightedAdjectives = {
+ stone: 10,
+ wooden: 1,
+ lengthy: 2,
+ formidable: 2,
+ rickety: 1,
+ beaten: 1,
+ weathered: 1
+ };
+ notes.push({id, name: `${name} Bridge`, legend: `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}`});
+ }
+ function listInns({cells}) {
+ return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.road[i] > 4 && cells.pop[i] > 10);
+ }
+
+ function addInn(id, cell) {
const colors = [
"Dark",
"Light",
@@ -430,44 +428,34 @@ window.Markers = (function () {
"sap"
];
- while (quantity) {
- const [cell] = extractAnyElement(taverns);
- const id = addMarker({cell, icon, type, px: 14});
- const typeName = P(0.3) ? "inn" : "tavern";
- const isAnimalThemed = P(0.7);
- const animal = ra(animals);
- const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(type);
- const meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
- const course = `${ra(methods)} ${meal}`.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`;
- notes.push({id, name: "The " + name, legend});
- quantity--;
- }
+ const typeName = P(0.3) ? "inn" : "tavern";
+ const isAnimalThemed = P(0.7);
+ const animal = ra(animals);
+ 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 course = `${ra(methods)} ${meal}`.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`;
+ notes.push({id, name: "The " + name, legend});
}
- function addLighthouses(type, icon, multiplier) {
- 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]);
- notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to serve as a beacon for ships in the open sea`});
- quantity--;
- }
+ 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 addWaterfalls(type, icon, multiplier) {
+ function addLighthouse(id, cell) {
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 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`});
+ }
+
+ 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 descriptions = [
"A gorgeous waterfall flows here",
@@ -477,98 +465,75 @@ window.Markers = (function () {
"A river drops down from a great height forming a wonderous waterfall",
"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]);
- notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend: `${ra(descriptions)}`});
- }
+
+ 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)}`});
}
- function addBattlefields(type, icon, multiplier) {
+ 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 addBattlefield(id, cell) {
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]];
- if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
- const campaign = ra(state.campaigns);
- const date = generateDate(campaign.start, campaign.end);
- const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
- const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const state = states[cells.state[cell]];
+ if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
+ const campaign = ra(state.campaigns);
+ const date = generateDate(campaign.start, campaign.end);
+ const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
+ const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
+ notes.push({id, name, legend});
}
- function addDungeons(type, icon, multiplier) {
+ function listDungeons({cells}) {
+ return cells.i.filter(i => !occupied[i] && cells.pop[i] && cells.pop[i] < 3);
+ }
+
+ function addDungeon(id, cell) {
+ const dungeonSeed = `${seed}${cell}`;
+ const name = "Dungeon";
+ const legend = `
`;
+ notes.push({id, name, legend});
+ }
+
+ function listLakeMonsters({features}) {
+ return features
+ .filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell])
+ .map(feature => feature.firstCell);
+ }
+
+ function addLakeMonster(id, cell) {
+ const lake = pack.features[pack.cells.f[cell]];
+
+ // Check that the feature is a lake in case the user clicked on a wrong
+ // square
+ if (lake.type !== "lake") return;
+
+ const name = `${lake.name} Monster`;
+ 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`;
+ notes.push({id, name, legend});
+ }
+
+ function listSeaMonsters({cells, features}) {
+ return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === "ocean");
+ }
+
+ function addSeaMonster(id, cell) {
+ const name = `${Names.getCultureShort(0)} Monster`;
+ 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`;
+ notes.push({id, name, legend});
+ }
+
+ function listHillMonsters({cells}) {
+ return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
+ }
+
+ function addHillMonster(id, cell) {
const {cells} = pack;
- 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});
-
- const dungeonSeed = `${seed}${cell}`;
- const name = "Dungeon";
- const legend = `
`;
- notes.push({id, name, legend});
- quantity--;
- }
- }
-
- function addLakeMonsters(type, icon, multiplier) {
- const {features} = pack;
-
- const lakes = features.filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell]);
- let quantity = getQuantity(lakes, 2, 10, multiplier);
- if (!quantity) 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 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`;
- notes.push({id, name, legend});
- quantity--;
- }
- }
-
- function addSeaMonsters(type, icon, multiplier) {
- const {cells, features} = pack;
-
- const sea = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === "ocean"));
- 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 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`;
- notes.push({id, name, legend});
- quantity--;
- }
- }
-
- function addHillMonsters(type, icon, multiplier) {
- 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 = [
"great",
"big",
@@ -619,100 +584,77 @@ window.Markers = (function () {
"attacks unsuspecting victims"
];
- while (quantity) {
- const [cell] = extractAnyElement(hills);
- const id = addMarker({cell, icon, type, dy: 54, px: 13});
- const monster = ra(species);
- const toponym = Names.getCulture(cells.culture[cell]);
- const name = `${toponym} ${monster}`;
- const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const monster = ra(species);
+ const toponym = Names.getCulture(cells.culture[cell]);
+ const name = `${toponym} ${monster}`;
+ const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`;
+ notes.push({id, name, legend});
}
- 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;
- 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 name = `${Names.getCulture(culture)} Mountain`;
- const height = getFriendlyHeight(cells.p[cell]);
- const legend = `A sacred mountain of ${cultures[culture].name} culture. Height: ${height}`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c);
+ const name = `${Names.getCulture(culture)} Mountain`;
+ const height = getFriendlyHeight(cells.p[cell]);
+ const legend = `A sacred mountain of ${cultures[culture].name} culture. Height: ${height}`;
+ notes.push({id, name, legend});
}
- 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;
- 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 name = `${Names.getCulture(culture)} Forest`;
- const legend = `A sacred forest of ${cultures[culture].name} culture`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const culture = cells.culture[cell];
+ const name = `${Names.getCulture(culture)} Forest`;
+ const legend = `A sacred forest of ${cultures[culture].name} culture`;
+ notes.push({id, name, legend});
}
- 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;
- 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 name = `${Names.getCulture(culture)} Pinery`;
- const legend = `A sacred pinery of ${cultures[culture].name} culture`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const culture = cells.culture[cell];
+ const name = `${Names.getCulture(culture)} Pinery`;
+ const legend = `A sacred pinery of ${cultures[culture].name} culture`;
+ notes.push({id, name, legend});
}
- 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;
- 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 name = `${Names.getCulture(culture)} Palm Grove`;
- const legend = `A sacred palm grove of ${cultures[culture].name} culture`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const culture = cells.culture[cell];
+ const name = `${Names.getCulture(culture)} Palm Grove`;
+ const legend = `A sacred palm grove of ${cultures[culture].name} culture`;
+ notes.push({id, name, legend});
}
- 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;
- 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 = [
"Apes",
"Badgers",
@@ -747,53 +689,43 @@ window.Markers = (function () {
];
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 biome = cells.biome[cell];
- const height = cells.p[cell];
- const locality =
- height >= 70
- ? "highlander"
- : [1, 2].includes(biome)
- ? "desert"
- : [3, 4].includes(biome)
- ? "mounted"
- : [5, 6, 7, 8, 9].includes(biome)
- ? "forest"
- : biome === 12
- ? "swamp"
- : "angry";
- const name = `${Names.getCulture(culture)} ${ra(animals)}`;
- const legend = `A gang of ${locality} ${rw(types)}`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const culture = cells.culture[cell];
+ const biome = cells.biome[cell];
+ const height = cells.p[cell];
+ const locality =
+ height >= 70
+ ? "highlander"
+ : [1, 2].includes(biome)
+ ? "desert"
+ : [3, 4].includes(biome)
+ ? "mounted"
+ : [5, 6, 7, 8, 9].includes(biome)
+ ? "forest"
+ : biome === 12
+ ? "swamp"
+ : "angry";
+ const name = `${Names.getCulture(culture)} ${ra(animals)}`;
+ const legend = `A gang of ${locality} ${rw(types)}`;
+ notes.push({id, name, legend});
}
- function addPirates(type, icon, multiplier) {
- const {cells} = pack;
-
- let searoutes = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i]));
- 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 legend = `Pirate ships have been spotted in these waters`;
- notes.push({id, name, legend});
- quantity--;
- }
+ // Pirates spawn on sea routes
+ function listPirates({cells}) {
+ return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i]);
}
- function addStatues(type, icon, multiplier) {
+ function addPirates(id, cell) {
+ const name = `Pirates`;
+ const legend = `Pirate ships have been spotted in these waters`;
+ notes.push({id, name, legend});
+ }
+
+ 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;
- 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 scripts = {
@@ -804,31 +736,25 @@ window.Markers = (function () {
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 name = `${Names.getCulture(culture)} ${variant}`;
- const script = scripts[ra(Object.keys(scripts))];
- const inscription = Array(rand(40, 100))
- .fill(null)
- .map(() => ra(script))
- .join("");
- const legend = `An ancient ${variant.toLowerCase()}. It has an inscription, but no one can translate it:
+ const variant = ra(variants);
+ const name = `${Names.getCulture(culture)} ${variant}`;
+ const script = scripts[ra(Object.keys(scripts))];
+ const inscription = Array(rand(40, 100))
+ .fill(null)
+ .map(() => ra(script))
+ .join("");
+ const legend = `An ancient ${variant.toLowerCase()}. It has an inscription, but no one can translate it:
${inscription}
`;
- notes.push({id, name, legend});
- quantity--;
- }
+ notes.push({id, name, legend});
}
- function addRuines(type, icon, multiplier) {
- const {cells} = pack;
- 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 listRuins({cells}) {
+ return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60);
+ }
+ function addRuins(id, cell) {
const types = [
"City",
"Town",
@@ -845,37 +771,30 @@ window.Markers = (function () {
"Castle"
];
- while (quantity) {
- const [cell] = extractAnyElement(ruins);
- const id = addMarker({cell, icon, type});
-
- const ruinType = ra(types);
- const name = `Ruined ${ruinType}`;
- const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
- notes.push({id, name, legend});
- quantity--;
- }
+ const ruinType = ra(types);
+ const name = `Ruined ${ruinType}`;
+ const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
+ notes.push({id, name, legend});
}
- function addPortals(type, icon, multiplier) {
- const {burgs} = pack;
- let portals = burgs
+ function listPortals({burgs}) {
+ return burgs
.slice(1, Math.ceil(burgs.length / 10) + 1)
- .filter(({cell}) => cell && !occupied[cell])
- .map(burg => [burg.name, burg.cell]);
- let quantity = getQuantity(portals, 16, 8, multiplier);
- if (!quantity) return;
-
- while (quantity) {
- const [portal] = extractAnyElement(portals);
- const [burgName, cell] = portal;
- const id = addMarker({cell, icon, type, px: 14});
- const name = `${burgName} Portal`;
- 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});
- quantity--;
- }
+ .filter(({cell}) => !occupied[cell])
+ .map(burg => burg.cell);
}
- return {generate, regenerate, getConfig, setConfig};
+ 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;
+
+ const name = `${burgName} Portal`;
+ 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});
+ }
+
+ return {add, generate, regenerate, getConfig, setConfig};
})();
diff --git a/modules/ui/layers.js b/modules/ui/layers.js
index 653cc9e4..009828c4 100644
--- a/modules/ui/layers.js
+++ b/modules/ui/layers.js
@@ -1481,6 +1481,12 @@ function drawRivers() {
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
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 path = getRiverPath(meanderedPoints, widthFactor, sourceWidth);
return `
`;
diff --git a/modules/ui/notes-editor.js b/modules/ui/notes-editor.js
index 872437fa..b985a5e8 100644
--- a/modules/ui/notes-editor.js
+++ b/modules/ui/notes-editor.js
@@ -86,8 +86,8 @@ function editNotes(id, name) {
selector: "#notesLegend",
height: "90%",
menubar: false,
- plugins: `autolink lists link charmap print formatpainter casechange code fullscreen image link media table paste hr checklist 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`,
+ plugins: `autolink lists link charmap print code fullscreen image link media table paste hr wordcount`,
+ 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_poster: false,
setup: editor => {
diff --git a/modules/ui/options.js b/modules/ui/options.js
index b6658490..ad90d7fe 100644
--- a/modules/ui/options.js
+++ b/modules/ui/options.js
@@ -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,
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,
- 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
.replace(/(?:\r\n|\r|\n)/g, "")
@@ -579,17 +579,18 @@ function randomizeOptions() {
function randomizeHeightmapTemplate() {
const templates = {
volcano: 3,
- highIsland: 22,
+ highIsland: 19,
lowIsland: 9,
- continents: 19,
- archipelago: 23,
+ continents: 16,
+ archipelago: 18,
mediterranean: 5,
peninsula: 3,
pangea: 5,
isthmus: 2,
atoll: 1,
shattered: 7,
- taklamakan: 1
+ taklamakan: 1,
+ oldWorld: 11
};
document.getElementById("templateInput").value = rw(templates);
}
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index aeed31ff..aec92311 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -706,14 +706,16 @@ function addMarkerOnClick() {
const point = d3.mouse(this);
const x = rn(point[0], 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 selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null;
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 rescale = +markersElement.getAttribute("rescale");
markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale));