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,112 +120,86 @@ 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}) {
const {cells} = pack; return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70);
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 addHotSprings(type, icon, multiplier) { function addVolcano(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])); const proper = Names.getCulture(cells.culture[cell]);
let quantity = getQuantity(springs, 30, 1200, multiplier); const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper;
if (!quantity) return; notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight(cells.p[cell])}`});
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--;
}
} }
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; const {cells} = pack;
let hillyBurgs = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] > 47 && cells.burg[i])); const proper = Names.getCulture(cells.culture[cell]);
let quantity = getQuantity(hillyBurgs, 1, 15, multiplier); const temp = convertTemperature(gauss(35, 15, 20, 100));
if (!quantity) return; 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}; const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1};
const resource = rw(resources);
while (quantity && hillyBurgs.length) { const burg = pack.burgs[cells.burg[cell]];
const [cell] = extractAnyElement(hillyBurgs); const name = `${burg.name}${resource} mining town`;
const id = addMarker({cell, icon, type, dx: 48, px: 13}); const population = rn(burg.population * populationRate * urbanization);
const resource = rw(resources); const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
const burg = pack.burgs[cells.burg[cell]]; notes.push({id, name, legend});
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--;
}
} }
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;
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; 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)); const burg = pack.burgs[cells.burg[cell]];
let quantity = getQuantity(taverns, 1, 100, multiplier); const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
if (!quantity) return; 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 = [ const colors = [
"Dark", "Dark",
"Light", "Light",
@ -430,44 +428,34 @@ window.Markers = (function () {
"sap" "sap"
]; ];
while (quantity) { const typeName = P(0.3) ? "inn" : "tavern";
const [cell] = extractAnyElement(taverns); const isAnimalThemed = P(0.7);
const id = addMarker({cell, icon, type, px: 14}); const animal = ra(animals);
const typeName = P(0.3) ? "inn" : "tavern"; const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(typeName);
const isAnimalThemed = P(0.7); const meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
const animal = ra(animals); const course = `${ra(methods)} ${meal}`.toLowerCase();
const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(type); const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase();
const meal = isAnimalThemed && P(0.3) ? animal : ra(courses); const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here`;
const course = `${ra(methods)} ${meal}`.toLowerCase(); notes.push({id, name: "The " + name, legend});
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--;
}
} }
function addLighthouses(type, icon, multiplier) { function listLighthouses({cells}) {
const {cells} = pack; return cells.i.filter(i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c]));
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 addWaterfalls(type, icon, multiplier) { function addLighthouse(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 proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
const quantity = getQuantity(waterfalls, 1, 5, multiplier); notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to serve as a beacon for ships in the open sea`});
if (!quantity) return; }
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 = [ const descriptions = [
"A gorgeous waterfall flows here", "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 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 proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
const id = addMarker({cell, icon, type, dy: 54, px: 16}); 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; 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)); const state = states[cells.state[cell]];
let quantity = getQuantity(battlefields, 50, 700, multiplier); if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
if (!quantity) return; const campaign = ra(state.campaigns);
const date = generateDate(campaign.start, campaign.end);
while (quantity && battlefields.length) { const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
const [cell] = extractAnyElement(battlefields); const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
const id = addMarker({cell, icon, type, dy: 52}); notes.push({id, name, legend});
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--;
}
} }
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 = `<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});
}
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; 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 = `<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});
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 = [ const adjectives = [
"great", "great",
"big", "big",
@ -619,100 +584,77 @@ window.Markers = (function () {
"attacks unsuspecting victims" "attacks unsuspecting victims"
]; ];
while (quantity) { const monster = ra(species);
const [cell] = extractAnyElement(hills); const toponym = Names.getCulture(cells.culture[cell]);
const id = addMarker({cell, icon, type, dy: 54, px: 13}); const name = `${toponym} ${monster}`;
const monster = ra(species); const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`;
const toponym = Names.getCulture(cells.culture[cell]); notes.push({id, name, legend});
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--;
}
} }
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( const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c);
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)) const name = `${Names.getCulture(culture)} Mountain`;
); const height = getFriendlyHeight(cells.p[cell]);
let quantity = getQuantity(lonelyMountains, 1, 5, multiplier); const legend = `A sacred mountain of ${cultures[culture].name} culture. Height: ${height}`;
if (!quantity) return; notes.push({id, name, legend});
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--;
}
} }
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]))); const culture = cells.culture[cell];
let quantity = getQuantity(temperateForests, 30, 1000, multiplier); const name = `${Names.getCulture(culture)} Forest`;
if (!quantity) return; const legend = `A sacred forest of ${cultures[culture].name} culture`;
notes.push({id, name, legend});
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--;
}
} }
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)); const culture = cells.culture[cell];
let quantity = getQuantity(borealForests, 30, 800, multiplier); const name = `${Names.getCulture(culture)} Pinery`;
if (!quantity) return; const legend = `A sacred pinery of ${cultures[culture].name} culture`;
notes.push({id, name, legend});
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--;
}
} }
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])); const culture = cells.culture[cell];
let quantity = getQuantity(oasises, 1, 100, multiplier); const name = `${Names.getCulture(culture)} Palm Grove`;
if (!quantity) return; const legend = `A sacred palm grove of ${cultures[culture].name} culture`;
notes.push({id, name, legend});
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--;
}
} }
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,53 +689,43 @@ 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 culture = cells.culture[cell];
const [cell] = extractAnyElement(roads); const biome = cells.biome[cell];
const id = addMarker({cell, icon, type, px: 13}); const height = cells.p[cell];
const culture = cells.culture[cell]; const locality =
const biome = cells.biome[cell]; height >= 70
const height = cells.p[cell]; ? "highlander"
const locality = : [1, 2].includes(biome)
height >= 70 ? "desert"
? "highlander" : [3, 4].includes(biome)
: [1, 2].includes(biome) ? "mounted"
? "desert" : [5, 6, 7, 8, 9].includes(biome)
: [3, 4].includes(biome) ? "forest"
? "mounted" : biome === 12
: [5, 6, 7, 8, 9].includes(biome) ? "swamp"
? "forest" : "angry";
: biome === 12 const name = `${Names.getCulture(culture)} ${ra(animals)}`;
? "swamp" const legend = `A gang of ${locality} ${rw(types)}`;
: "angry"; notes.push({id, name, legend});
const name = `${Names.getCulture(culture)} ${ra(animals)}`;
const legend = `A gang of ${locality} ${rw(types)}`;
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]));
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--;
}
} }
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; 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,31 +736,25 @@ window.Markers = (function () {
mongolian: "᠀᠐᠑᠒ᠠᠡᠦᠧᠨᠩᠪᠭᠮᠯᠰᠱᠲᠳᠵᠻᠼᠽᠾᠿᡀᡁᡆᡍᡎᡏᡐᡑᡒᡓᡔᡕᡖᡗᡙᡜᡝᡞᡟᡠᡡᡭᡮᡯᡰᡱᡲᡳᡴᢀᢁᢂᢋᢏᢐᢑᢒᢓᢛᢜᢞᢟᢠᢡᢢᢤᢥᢦ" mongolian: "᠀᠐᠑᠒ᠠᠡᠦᠧᠨᠩᠪᠭᠮᠯᠰᠱᠲᠳᠵᠻᠼᠽᠾᠿᡀᡁᡆᡍᡎᡏᡐᡑᡒᡓᡔᡕᡖᡗᡙᡜᡝᡞᡟᡠᡡᡭᡮᡯᡰᡱᡲᡳᡴᢀᢁᢂᢋᢏᢐᢑᢒᢓᢛᢜᢞᢟᢠᢡᢢᢤᢥᢦ"
}; };
while (quantity) { const culture = cells.culture[cell];
const [cell] = extractAnyElement(statues);
const id = addMarker({cell, icon, type});
const culture = cells.culture[cell];
const variant = ra(variants); const variant = ra(variants);
const name = `${Names.getCulture(culture)} ${variant}`; const name = `${Names.getCulture(culture)} ${variant}`;
const script = scripts[ra(Object.keys(scripts))]; const script = scripts[ra(Object.keys(scripts))];
const inscription = Array(rand(40, 100)) const inscription = Array(rand(40, 100))
.fill(null) .fill(null)
.map(() => ra(script)) .map(() => ra(script))
.join(""); .join("");
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 ruinType = ra(types);
const [cell] = extractAnyElement(ruins); const name = `Ruined ${ruinType}`;
const id = addMarker({cell, icon, type}); const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
notes.push({id, name, legend});
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--;
}
} }
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;
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--;
}
} }
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};
})(); })();

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));