mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-02-05 01:51:23 +01:00
feat: Add numerous fantasy icons, grid numbering debug files, and update map rendering logic
This commit is contained in:
parent
9a16e06223
commit
093390aa6e
45 changed files with 17259 additions and 8108 deletions
|
|
@ -11,7 +11,7 @@ window.Markers = (function () {
|
|||
/*
|
||||
Default markers config:
|
||||
type - short description (snake-case)
|
||||
icon - unicode character or url to image
|
||||
icon - unicode character or url to image (using local fantasy icons)
|
||||
dx: icon offset in x direction, in pixels
|
||||
dy: icon offset in y direction, in pixels
|
||||
min: minimum number of candidates to add at least 1 marker
|
||||
|
|
@ -22,41 +22,41 @@ window.Markers = (function () {
|
|||
*/
|
||||
// prettier-ignore
|
||||
return [
|
||||
{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: "water-sources", icon: "💧", min: 1, each: 1000, multiplier: 1, list: listWaterSources, add: addWaterSource},
|
||||
{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: 10, 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: "ruins", icon: "🏺", min: 80, each: 1200, multiplier: 1, list: listRuins, add: addRuins},
|
||||
{type: "libraries", icon: "📚", min: 10, each: 1200, multiplier: 1, list: listLibraries, add: addLibrary},
|
||||
{type: "circuses", icon: "🎪", min: 80, each: 1000, multiplier: 1, list: listCircuses, add: addCircuse},
|
||||
{type: "jousts", icon: "🤺", dx: 48, min: 5, each: 500, multiplier: 1, list: listJousts, add: addJoust},
|
||||
{type: "fairs", icon: "🎠", min: 50, each: 1000, multiplier: 1, list: listFairs, add: addFair},
|
||||
{type: "canoes", icon: "🛶", min: 500, each: 2000, multiplier: 1, list: listCanoes, add: addCanoe},
|
||||
{type: "migration", icon: "🐗", min: 20, each: 1000, multiplier: 1, list: listMigrations, add: addMigration},
|
||||
{type: "dances", icon: "💃🏽", min: 50, each: 1000, multiplier: 1, list: listDances, add: addDances},
|
||||
{type: "mirage", icon: "💦", min: 10, each: 400, multiplier: 1, list: listMirage, add: addMirage},
|
||||
{type: "caves", icon:"🦇", min: 60, each: 1000, multiplier: 1, list: listCaves, add: addCave},
|
||||
{type: "portals", icon: "🌀", px: 14, min: 16, each: 8, multiplier: +isFantasy, list: listPortals, add: addPortal},
|
||||
{type: "rifts", icon: "🎆", min: 5, each: 3000, multiplier: +isFantasy, list: listRifts, add: addRift},
|
||||
{type: "disturbed-burials", icon: "💀", min: 20, each: 3000, multiplier: +isFantasy, list: listDisturbedBurial, add: addDisturbedBurial},
|
||||
{type: "necropolises", icon: "🪦", min: 20, each: 1000, multiplier: 1, list: listNecropolis, add: addNecropolis},
|
||||
{type: "encounters", icon: "🧙", min: 10, each: 600, multiplier: 1, list: listEncounters, add: addEncounter},
|
||||
{ type: "volcanoes", icon: "./images/fantasy-icons/volcano.svg", dx: 50, dy: 50, px: 16, min: 10, each: 500, multiplier: 1, list: listVolcanoes, add: addVolcano },
|
||||
{ type: "hot-springs", icon: "./images/fantasy-icons/hot-spring.svg", dx: 50, dy: 50, px: 16, min: 30, each: 1200, multiplier: 1, list: listHotSprings, add: addHotSpring },
|
||||
{ type: "water-sources", icon: "./images/fantasy-icons/water-source.svg", dx: 50, dy: 50, px: 16, min: 1, each: 1000, multiplier: 1, list: listWaterSources, add: addWaterSource },
|
||||
{ type: "mines", icon: "./images/fantasy-icons/mine.svg", dx: 50, dy: 50, px: 16, min: 1, each: 15, multiplier: 1, list: listMines, add: addMine },
|
||||
{ type: "bridges", icon: "./images/fantasy-icons/bridge.svg", dx: 50, dy: 50, px: 16, min: 1, each: 5, multiplier: 1, list: listBridges, add: addBridge },
|
||||
{ type: "inns", icon: "./images/fantasy-icons/inn.svg", dx: 50, dy: 50, px: 16, min: 1, each: 10, multiplier: 1, list: listInns, add: addInn },
|
||||
{ type: "lighthouses", icon: "./images/fantasy-icons/lighthouse.svg", dx: 50, dy: 50, px: 16, min: 1, each: 2, multiplier: 1, list: listLighthouses, add: addLighthouse },
|
||||
{ type: "waterfalls", icon: "./images/fantasy-icons/waterfall.svg", dx: 50, dy: 50, px: 16, min: 1, each: 5, multiplier: 1, list: listWaterfalls, add: addWaterfall },
|
||||
{ type: "battlefields", icon: "./images/fantasy-icons/battlefield.svg", dx: 50, dy: 50, px: 16, min: 50, each: 700, multiplier: 1, list: listBattlefields, add: addBattlefield },
|
||||
{ type: "dungeons", icon: "./images/fantasy-icons/dungeon.svg", dx: 50, dy: 50, px: 16, min: 30, each: 200, multiplier: 1, list: listDungeons, add: addDungeon },
|
||||
{ type: "lake-monsters", icon: "./images/fantasy-icons/monster.svg", dx: 50, dy: 50, px: 16, min: 2, each: 10, multiplier: 1, list: listLakeMonsters, add: addLakeMonster },
|
||||
{ type: "sea-monsters", icon: "./images/fantasy-icons/sea-monster.svg", dx: 50, dy: 50, px: 16, min: 50, each: 700, multiplier: 1, list: listSeaMonsters, add: addSeaMonster },
|
||||
{ type: "hill-monsters", icon: "./images/fantasy-icons/monster.svg", dx: 50, dy: 50, px: 16, min: 30, each: 600, multiplier: 1, list: listHillMonsters, add: addHillMonster },
|
||||
{ type: "sacred-mountains", icon: "./images/fantasy-icons/sacred-mountain.svg", dx: 50, dy: 50, px: 16, min: 1, each: 5, multiplier: 1, list: listSacredMountains, add: addSacredMountain },
|
||||
{ type: "sacred-forests", icon: "./images/fantasy-icons/forest.svg", dx: 50, dy: 50, px: 16, min: 30, each: 1000, multiplier: 1, list: listSacredForests, add: addSacredForest },
|
||||
{ type: "sacred-pineries", icon: "./images/fantasy-icons/forest.svg", dx: 50, dy: 50, px: 16, min: 30, each: 800, multiplier: 1, list: listSacredPineries, add: addSacredPinery },
|
||||
{ type: "sacred-palm-groves", icon: "./images/fantasy-icons/forest.svg", dx: 50, dy: 50, px: 16, min: 1, each: 100, multiplier: 1, list: listSacredPalmGroves, add: addSacredPalmGrove },
|
||||
{ type: "brigands", icon: "./images/fantasy-icons/brigand.svg", dx: 50, dy: 50, px: 16, min: 50, each: 100, multiplier: 1, list: listBrigands, add: addBrigands },
|
||||
{ type: "pirates", icon: "./images/fantasy-icons/pirate.svg", dx: 50, dy: 50, px: 16, min: 40, each: 300, multiplier: 1, list: listPirates, add: addPirates },
|
||||
{ type: "statues", icon: "./images/fantasy-icons/statue.svg", dx: 50, dy: 50, px: 16, min: 80, each: 1200, multiplier: 1, list: listStatues, add: addStatue },
|
||||
{ type: "ruins", icon: "./images/fantasy-icons/ruins.svg", dx: 50, dy: 50, px: 16, min: 80, each: 1200, multiplier: 1, list: listRuins, add: addRuins },
|
||||
{ type: "libraries", icon: "./images/fantasy-icons/library.svg", dx: 50, dy: 50, px: 16, min: 10, each: 1200, multiplier: 1, list: listLibraries, add: addLibrary },
|
||||
{ type: "circuses", icon: "./images/fantasy-icons/circus.svg", dx: 50, dy: 50, px: 16, min: 80, each: 1000, multiplier: 1, list: listCircuses, add: addCircuse },
|
||||
{ type: "jousts", icon: "./images/fantasy-icons/joust.svg", dx: 50, dy: 50, px: 16, min: 5, each: 500, multiplier: 1, list: listJousts, add: addJoust },
|
||||
{ type: "fairs", icon: "./images/fantasy-icons/fair.svg", dx: 50, dy: 50, px: 16, min: 50, each: 1000, multiplier: 1, list: listFairs, add: addFair },
|
||||
{ type: "canoes", icon: "./images/fantasy-icons/canoe.svg", dx: 50, dy: 50, px: 16, min: 500, each: 2000, multiplier: 1, list: listCanoes, add: addCanoe },
|
||||
{ type: "migration", icon: "./images/fantasy-icons/migration.svg", dx: 50, dy: 50, px: 16, min: 20, each: 1000, multiplier: 1, list: listMigrations, add: addMigration },
|
||||
{ type: "dances", icon: "./images/fantasy-icons/dance.svg", dx: 50, dy: 50, px: 16, min: 50, each: 1000, multiplier: 1, list: listDances, add: addDances },
|
||||
{ type: "mirage", icon: "./images/fantasy-icons/mirage.svg", dx: 50, dy: 50, px: 16, min: 10, each: 400, multiplier: 1, list: listMirage, add: addMirage },
|
||||
{ type: "caves", icon: "./images/fantasy-icons/cave.svg", dx: 50, dy: 50, px: 16, min: 60, each: 1000, multiplier: 1, list: listCaves, add: addCave },
|
||||
{ type: "portals", icon: "./images/fantasy-icons/portal.svg", dx: 50, dy: 50, px: 16, min: 16, each: 8, multiplier: +isFantasy, list: listPortals, add: addPortal },
|
||||
{ type: "rifts", icon: "./images/fantasy-icons/rift.svg", dx: 50, dy: 50, px: 16, min: 5, each: 3000, multiplier: +isFantasy, list: listRifts, add: addRift },
|
||||
{ type: "disturbed-burials", icon: "./images/fantasy-icons/burial.svg", dx: 50, dy: 50, px: 16, min: 20, each: 3000, multiplier: +isFantasy, list: listDisturbedBurial, add: addDisturbedBurial },
|
||||
{ type: "necropolises", icon: "./images/fantasy-icons/necropolis.svg", dx: 50, dy: 50, px: 16, min: 20, each: 1000, multiplier: 1, list: listNecropolis, add: addNecropolis },
|
||||
{ type: "encounters", icon: "./images/fantasy-icons/encounter.svg", dx: 50, dy: 50, px: 16, min: 10, each: 600, multiplier: 1, list: listEncounters, add: addEncounter },
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ window.Markers = (function () {
|
|||
};
|
||||
|
||||
const regenerate = () => {
|
||||
pack.markers = pack.markers.filter(({i, lock, cell}) => {
|
||||
pack.markers = pack.markers.filter(({ i, lock, cell }) => {
|
||||
if (lock) {
|
||||
occupied[cell] = true;
|
||||
return true;
|
||||
|
|
@ -91,22 +91,22 @@ window.Markers = (function () {
|
|||
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);
|
||||
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});
|
||||
pack.markers.push({ ...marker, i });
|
||||
occupied[marker.cell] = true;
|
||||
return {...marker, i};
|
||||
return { ...marker, i };
|
||||
};
|
||||
|
||||
function generateTypes() {
|
||||
TIME && console.time("addMarkers");
|
||||
|
||||
config.forEach(({type, icon, dx, dy, px, min, each, multiplier, list, add}) => {
|
||||
config.forEach(({ type, icon, dx, dy, px, min, each, multiplier, list, add }) => {
|
||||
if (multiplier === 0) return;
|
||||
|
||||
let candidates = Array.from(list(pack));
|
||||
|
|
@ -116,7 +116,7 @@ window.Markers = (function () {
|
|||
|
||||
while (quantity && candidates.length) {
|
||||
const [cell] = extractAnyElement(candidates);
|
||||
const marker = addMarker({icon, type, dx, dy, px}, {cell});
|
||||
const marker = addMarker({ icon, type, dx, dy, px }, { cell });
|
||||
if (!marker) continue;
|
||||
add("marker" + marker.i, cell);
|
||||
quantity--;
|
||||
|
|
@ -139,11 +139,11 @@ window.Markers = (function () {
|
|||
}
|
||||
|
||||
function getMarkerCoordinates(cell) {
|
||||
const {cells, burgs} = pack;
|
||||
const { cells, burgs } = pack;
|
||||
const burgId = cells.burg[cell];
|
||||
|
||||
if (burgId) {
|
||||
const {x, y} = burgs[burgId];
|
||||
const { x, y } = burgs[burgId];
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ window.Markers = (function () {
|
|||
if (marker.cell === undefined) return;
|
||||
const i = last(pack.markers)?.i + 1 || 0;
|
||||
const [x, y] = getMarkerCoordinates(marker.cell);
|
||||
marker = {...base, x, y, ...marker, i};
|
||||
marker = { ...base, x, y, ...marker, i };
|
||||
pack.markers.push(marker);
|
||||
occupied[marker.cell] = true;
|
||||
return marker;
|
||||
|
|
@ -166,40 +166,40 @@ window.Markers = (function () {
|
|||
pack.markers = pack.markers.filter(m => m.i !== markerId);
|
||||
}
|
||||
|
||||
function listVolcanoes({cells}) {
|
||||
function listVolcanoes({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70);
|
||||
}
|
||||
|
||||
function addVolcano(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const name = P(0.3) ? "Mount " + proper : P(0.7) ? proper + " Volcano" : proper;
|
||||
const status = P(0.6) ? "Dormant" : P(0.4) ? "Active" : "Erupting";
|
||||
notes.push({id, name, legend: `${status} volcano. Height: ${getFriendlyHeight(cells.p[cell])}.`});
|
||||
notes.push({ id, name, legend: `${status} volcano. Height: ${getFriendlyHeight(cells.p[cell])}.` });
|
||||
}
|
||||
|
||||
function listHotSprings({cells}) {
|
||||
function listHotSprings({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] > 50 && cells.culture[i]);
|
||||
}
|
||||
|
||||
function addHotSpring(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const temp = convertTemperature(gauss(35, 15, 20, 100));
|
||||
const name = P(0.3) ? "Hot Springs of " + proper : P(0.7) ? proper + " Hot Springs" : proper;
|
||||
const legend = `A geothermal springs with naturally heated water that provide relaxation and medicinal benefits. Average temperature is ${temp}.`;
|
||||
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listWaterSources({cells}) {
|
||||
function listWaterSources({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] > 30 && cells.r[i]);
|
||||
}
|
||||
|
||||
function addWaterSource(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const type = rw({
|
||||
"Healing Spring": 5,
|
||||
|
|
@ -218,26 +218,26 @@ window.Markers = (function () {
|
|||
const legend =
|
||||
"This legendary water source is whispered about in ancient tales and believed to possess mystical properties. The spring emanates crystal-clear water, shimmering with an otherworldly iridescence that sparkles even in the dimmest light.";
|
||||
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listMines({cells}) {
|
||||
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;
|
||||
|
||||
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);
|
||||
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});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listBridges({cells, burgs}) {
|
||||
function listBridges({ cells, burgs }) {
|
||||
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
|
||||
return cells.i.filter(
|
||||
i =>
|
||||
|
|
@ -251,7 +251,7 @@ window.Markers = (function () {
|
|||
}
|
||||
|
||||
function addBridge(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
|
||||
|
|
@ -277,10 +277,10 @@ window.Markers = (function () {
|
|||
? `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}.`
|
||||
: `An old crossing of the ${riverName}, rarely used since ${ra(barriers)}.`;
|
||||
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listInns({cells}) {
|
||||
function listInns({ cells }) {
|
||||
const crossRoads = cells.i.filter(i => !occupied[i] && cells.pop[i] > 5 && Routes.isCrossroad(i));
|
||||
return crossRoads;
|
||||
}
|
||||
|
|
@ -540,17 +540,17 @@ window.Markers = (function () {
|
|||
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});
|
||||
notes.push({ id, name: "The " + name, legend });
|
||||
}
|
||||
|
||||
function listLighthouses({cells}) {
|
||||
function listLighthouses({ cells }) {
|
||||
return cells.i.filter(
|
||||
i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && Routes.isConnected(c))
|
||||
);
|
||||
}
|
||||
|
||||
function addLighthouse(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({
|
||||
|
|
@ -560,14 +560,14 @@ window.Markers = (function () {
|
|||
});
|
||||
}
|
||||
|
||||
function listWaterfalls({cells}) {
|
||||
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 descriptions = [
|
||||
"A gorgeous waterfall flows here.",
|
||||
|
|
@ -579,17 +579,17 @@ window.Markers = (function () {
|
|||
];
|
||||
|
||||
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}) {
|
||||
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;
|
||||
|
||||
const state = states[cells.state[cell]];
|
||||
if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
|
||||
|
|
@ -597,10 +597,10 @@ window.Markers = (function () {
|
|||
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});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listDungeons({cells}) {
|
||||
function listDungeons({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.pop[i] && cells.pop[i] < 3);
|
||||
}
|
||||
|
||||
|
|
@ -608,10 +608,10 @@ window.Markers = (function () {
|
|||
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 style="pointer-events: none;" 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 });
|
||||
}
|
||||
|
||||
function listLakeMonsters({features}) {
|
||||
function listLakeMonsters({ features }) {
|
||||
return features
|
||||
.filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell])
|
||||
.map(feature => feature.firstCell);
|
||||
|
|
@ -637,13 +637,12 @@ window.Markers = (function () {
|
|||
"Journeying folk",
|
||||
"Tales"
|
||||
];
|
||||
const legend = `${ra(subjects)} 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});
|
||||
const legend = `${ra(subjects)} 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}) {
|
||||
function listSeaMonsters({ cells, features }) {
|
||||
return cells.i.filter(
|
||||
i => !occupied[i] && cells.h[i] < 20 && Routes.isConnected(i) && features[cells.f[i]].type === "ocean"
|
||||
);
|
||||
|
|
@ -653,15 +652,15 @@ window.Markers = (function () {
|
|||
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});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listHillMonsters({cells}) {
|
||||
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 adjectives = [
|
||||
"great",
|
||||
|
|
@ -729,11 +728,11 @@ window.Markers = (function () {
|
|||
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 });
|
||||
}
|
||||
|
||||
// Sacred mountains spawn on lonely mountains
|
||||
function listSacredMountains({cells}) {
|
||||
function listSacredMountains({ cells }) {
|
||||
return cells.i.filter(
|
||||
i =>
|
||||
!occupied[i] &&
|
||||
|
|
@ -744,50 +743,50 @@ window.Markers = (function () {
|
|||
}
|
||||
|
||||
function addSacredMountain(id, cell) {
|
||||
const {cells, religions} = pack;
|
||||
const { cells, religions } = pack;
|
||||
|
||||
const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c);
|
||||
const religion = cells.religion[cell];
|
||||
const name = `${Names.getCulture(culture)} Mountain`;
|
||||
const height = getFriendlyHeight(cells.p[cell]);
|
||||
const legend = `A sacred mountain of ${religions[religion].name}. Height: ${height}.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
// Sacred forests spawn on temperate forests
|
||||
function listSacredForests({cells}) {
|
||||
function listSacredForests({ cells }) {
|
||||
return cells.i.filter(
|
||||
i => !occupied[i] && cells.culture[i] && cells.religion[i] && [6, 8].includes(cells.biome[i])
|
||||
);
|
||||
}
|
||||
|
||||
function addSacredForest(id, cell) {
|
||||
const {cells, religions} = pack;
|
||||
const { cells, religions } = pack;
|
||||
|
||||
const culture = cells.culture[cell];
|
||||
const religion = cells.religion[cell];
|
||||
const name = `${Names.getCulture(culture)} Forest`;
|
||||
const legend = `A forest sacred to local ${religions[religion].name}.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
// Sacred pineries spawn on boreal forests
|
||||
function listSacredPineries({cells}) {
|
||||
function listSacredPineries({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.religion[i] && cells.biome[i] === 9);
|
||||
}
|
||||
|
||||
function addSacredPinery(id, cell) {
|
||||
const {cells, religions} = pack;
|
||||
const { cells, religions } = pack;
|
||||
|
||||
const culture = cells.culture[cell];
|
||||
const religion = cells.religion[cell];
|
||||
const name = `${Names.getCulture(culture)} Pinery`;
|
||||
const legend = `A pinery sacred to local ${religions[religion].name}.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
// Sacred palm groves spawn on oasises
|
||||
function listSacredPalmGroves({cells}) {
|
||||
function listSacredPalmGroves({ cells }) {
|
||||
return cells.i.filter(
|
||||
i =>
|
||||
!occupied[i] &&
|
||||
|
|
@ -800,21 +799,21 @@ window.Markers = (function () {
|
|||
}
|
||||
|
||||
function addSacredPalmGrove(id, cell) {
|
||||
const {cells, religions} = pack;
|
||||
const { cells, religions } = pack;
|
||||
|
||||
const culture = cells.culture[cell];
|
||||
const religion = cells.religion[cell];
|
||||
const name = `${Names.getCulture(culture)} Palm Grove`;
|
||||
const legend = `A palm grove sacred to local ${religions[religion].name}.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listBrigands({cells}) {
|
||||
function listBrigands({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && Routes.hasRoad(i));
|
||||
}
|
||||
|
||||
function addBrigands(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const animals = [
|
||||
"Apes",
|
||||
|
|
@ -848,7 +847,7 @@ window.Markers = (function () {
|
|||
"Wolverines",
|
||||
"Falcons"
|
||||
];
|
||||
const types = {brigands: 4, bandits: 3, robbers: 1, highwaymen: 1};
|
||||
const types = { brigands: 4, bandits: 3, robbers: 1, highwaymen: 1 };
|
||||
|
||||
const culture = cells.culture[cell];
|
||||
const biome = cells.biome[cell];
|
||||
|
|
@ -865,26 +864,26 @@ window.Markers = (function () {
|
|||
|
||||
const name = `${Names.getCulture(culture)} ${ra(animals)}`;
|
||||
const legend = `A gang of ${locality} ${rw(types)}.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
// Pirates spawn on sea routes
|
||||
function listPirates({cells}) {
|
||||
function listPirates({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && Routes.isConnected(i));
|
||||
}
|
||||
|
||||
function addPirates(id, cell) {
|
||||
const name = "Pirates";
|
||||
const legend = "Pirate ships have been spotted in these waters.";
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listStatues({cells}) {
|
||||
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;
|
||||
|
||||
const variants = [
|
||||
"Statue",
|
||||
|
|
@ -919,10 +918,10 @@ window.Markers = (function () {
|
|||
.join("");
|
||||
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>`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listRuins({cells}) {
|
||||
function listRuins({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60);
|
||||
}
|
||||
|
||||
|
|
@ -946,24 +945,24 @@ window.Markers = (function () {
|
|||
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});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listLibraries({cells}) {
|
||||
function listLibraries({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.burg[i] && cells.pop[i] > 10);
|
||||
}
|
||||
|
||||
function addLibrary(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const type = rw({Library: 3, Archive: 1, Collection: 1});
|
||||
const type = rw({ Library: 3, Archive: 1, Collection: 1 });
|
||||
const name = `${Names.getCulture(cells.culture[cell])} ${type}`;
|
||||
const legend = "A vast collection of knowledge, including many rare and ancient tomes.";
|
||||
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listCircuses({cells}) {
|
||||
function listCircuses({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && Routes.isConnected(i));
|
||||
}
|
||||
|
||||
|
|
@ -982,15 +981,15 @@ window.Markers = (function () {
|
|||
const adjective = ra(adjectives);
|
||||
const name = `Travelling ${adjective} Circus`;
|
||||
const legend = `Roll up, roll up, this ${adjective.toLowerCase()} circus is here for a limited time only.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listJousts({cells, burgs}) {
|
||||
function listJousts({ cells, burgs }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population > 20);
|
||||
}
|
||||
|
||||
function addJoust(id, cell) {
|
||||
const {cells, burgs} = pack;
|
||||
const { cells, burgs } = pack;
|
||||
const types = ["Joust", "Competition", "Melee", "Tournament", "Contest"];
|
||||
const virtues = ["cunning", "might", "speed", "the greats", "acumen", "brutality"];
|
||||
|
||||
|
|
@ -1001,17 +1000,17 @@ window.Markers = (function () {
|
|||
|
||||
const name = `${burgName} ${type}`;
|
||||
const legend = `Warriors from around the land gather for a ${type.toLowerCase()} of ${virtue} in ${burgName}, with fame, fortune and favour on offer to the victor.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listFairs({cells, burgs}) {
|
||||
function listFairs({ cells, burgs }) {
|
||||
return cells.i.filter(
|
||||
i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population < 20 && burgs[cells.burg[i]].population < 5
|
||||
);
|
||||
}
|
||||
|
||||
function addFair(id, cell) {
|
||||
const {cells, burgs} = pack;
|
||||
const { cells, burgs } = pack;
|
||||
if (!cells.burg[cell]) return;
|
||||
|
||||
const burgName = burgs[cells.burg[cell]].name;
|
||||
|
|
@ -1019,10 +1018,10 @@ window.Markers = (function () {
|
|||
|
||||
const name = `${burgName} ${type}`;
|
||||
const legend = `A fair is being held in ${burgName}, with all manner of local and foreign goods and services on offer.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listCanoes({cells}) {
|
||||
function listCanoes({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.r[i]);
|
||||
}
|
||||
|
||||
|
|
@ -1032,10 +1031,10 @@ window.Markers = (function () {
|
|||
const name = `Minor Jetty`;
|
||||
const riverName = river ? `${river.name} ${river.type}` : "river";
|
||||
const legend = `A small location along the ${riverName} to launch boats from sits here, along with a weary looking owner, willing to sell passage along the river.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listMigrations({cells}) {
|
||||
function listMigrations({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] <= 2);
|
||||
}
|
||||
|
||||
|
|
@ -1097,15 +1096,15 @@ window.Markers = (function () {
|
|||
|
||||
const name = `${animalChoice} migration`;
|
||||
const legend = `A huge group of ${animalChoice.toLowerCase()} are migrating, whether part of their annual routine, or something more extraordinary.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listDances({cells, burgs}) {
|
||||
function listDances({ cells, burgs }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population > 15);
|
||||
}
|
||||
|
||||
function addDances(id, cell) {
|
||||
const {cells, burgs} = pack;
|
||||
const { cells, burgs } = pack;
|
||||
const burgName = burgs[cells.burg[cell]].name;
|
||||
const socialTypes = [
|
||||
"gala",
|
||||
|
|
@ -1136,10 +1135,10 @@ window.Markers = (function () {
|
|||
const legend = `A ${socialType} has been organised at ${burgName} as a chance to gather the ${ra(
|
||||
people
|
||||
)} of the area together to be merry, make alliances and scheme around the crisis.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listMirage({cells}) {
|
||||
function listMirage({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.biome[i] === 1);
|
||||
}
|
||||
|
||||
|
|
@ -1149,15 +1148,15 @@ window.Markers = (function () {
|
|||
const mirageAdjective = ra(adjectives);
|
||||
const name = `${mirageAdjective} mirage`;
|
||||
const legend = `This ${mirageAdjective.toLowerCase()} mirage has been luring travellers out of their way for eons.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listCaves({cells}) {
|
||||
function listCaves({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
|
||||
}
|
||||
|
||||
function addCave(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const formations = {
|
||||
Cave: 10,
|
||||
|
|
@ -1186,28 +1185,28 @@ window.Markers = (function () {
|
|||
}
|
||||
const name = `${toponym} ${formation}`;
|
||||
const legend = `The ${name}. Locals claim that it is ${rw(status)}.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listPortals({burgs}) {
|
||||
function listPortals({ burgs }) {
|
||||
return burgs
|
||||
.slice(1, Math.ceil(burgs.length / 10) + 1)
|
||||
.filter(({cell}) => !occupied[cell])
|
||||
.filter(({ cell }) => !occupied[cell])
|
||||
.map(burg => burg.cell);
|
||||
}
|
||||
|
||||
function addPortal(id, cell) {
|
||||
const {cells, burgs} = pack;
|
||||
const { cells, burgs } = pack;
|
||||
|
||||
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. The portals were installed centuries ago, but still work fine.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listRifts({cells}) {
|
||||
function listRifts({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && pack.cells.pop[i] <= 3 && biomesData.habitability[pack.cells.biome[i]]);
|
||||
}
|
||||
|
||||
|
|
@ -1225,24 +1224,24 @@ window.Markers = (function () {
|
|||
const riftType = ra(types);
|
||||
const name = `${riftType} Rift`;
|
||||
const legend = `A rumoured ${riftType.toLowerCase()} rift in this area is causing ${ra(descriptions)}.`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listDisturbedBurial({cells}) {
|
||||
function listDisturbedBurial({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] > 2);
|
||||
}
|
||||
function addDisturbedBurial(id, cell) {
|
||||
const name = "Disturbed Burial";
|
||||
const legend = "A burial site has been disturbed in this area, causing the dead to rise and attack the living.";
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listNecropolis({cells}) {
|
||||
function listNecropolis({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] < 2);
|
||||
}
|
||||
|
||||
function addNecropolis(id, cell) {
|
||||
const {cells} = pack;
|
||||
const { cells } = pack;
|
||||
|
||||
const toponym = Names.getCulture(cells.culture[cell]);
|
||||
const type = rw({
|
||||
|
|
@ -1269,10 +1268,10 @@ window.Markers = (function () {
|
|||
"A foreboding necropolis perched atop a jagged cliff, overlooking a desolate wasteland. Its towering walls harbor restless spirits, and the imposing gates bear the marks of countless battles and ancient curses."
|
||||
]);
|
||||
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
function listEncounters({cells}) {
|
||||
function listEncounters({ cells }) {
|
||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] > 1);
|
||||
}
|
||||
|
||||
|
|
@ -1280,8 +1279,8 @@ window.Markers = (function () {
|
|||
const name = "Random encounter";
|
||||
const encounterSeed = cell; // use just cell Id to not overwhelm the Vercel KV database
|
||||
const legend = `<div>You have encountered a character.</div><iframe src="https://deorum.vercel.app/encounter/${encounterSeed}" width="375" height="600" sandbox="allow-scripts allow-same-origin allow-popups"></iframe>`;
|
||||
notes.push({id, name, legend});
|
||||
notes.push({ id, name, legend });
|
||||
}
|
||||
|
||||
return {add, generate, regenerate, getConfig, setConfig, deleteMarker};
|
||||
return { add, generate, regenerate, getConfig, setConfig, deleteMarker };
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ function handleLayersPresetChange(preset) {
|
|||
}
|
||||
|
||||
function savePreset() {
|
||||
prompt("Please provide a preset name", {default: ""}, preset => {
|
||||
prompt("Please provide a preset name", { default: "" }, preset => {
|
||||
presets[preset] = Array.from(byId("mapLayers").querySelectorAll("li:not(.buttonoff)"))
|
||||
.map(node => node.id)
|
||||
.sort();
|
||||
|
|
@ -258,8 +258,8 @@ function drawBiomes() {
|
|||
|
||||
const cells = pack.cells;
|
||||
const bodyPaths = new Array(biomesData.i.length - 1);
|
||||
const isolines = getIsolines(pack, cellId => cells.biome[cellId], {fill: true, waterGap: true});
|
||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
||||
const isolines = getIsolines(pack, cellId => cells.biome[cellId], { fill: true, waterGap: true });
|
||||
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||
const color = biomesData.color[index];
|
||||
bodyPaths.push(getGappedFillPaths("biome", fill, waterGap, color, index));
|
||||
});
|
||||
|
|
@ -288,7 +288,7 @@ function drawPrecipitation() {
|
|||
TIME && console.time("drawPrecipitation");
|
||||
|
||||
prec.selectAll("circle").remove();
|
||||
const {cells, points} = grid;
|
||||
const { cells, points } = grid;
|
||||
|
||||
const show = d3.transition().duration(800).ease(d3.easeSinIn);
|
||||
prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1);
|
||||
|
|
@ -348,7 +348,7 @@ function togglePopulation(event) {
|
|||
function drawPopulation() {
|
||||
population.selectAll("line").remove();
|
||||
|
||||
const {cells, burgs} = pack;
|
||||
const { cells, burgs } = pack;
|
||||
const show = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||
|
||||
const rural = Array.from(
|
||||
|
|
@ -420,8 +420,8 @@ function toggleIce(event) {
|
|||
function drawIce() {
|
||||
TIME && console.time("drawIce");
|
||||
|
||||
const {cells, features} = grid;
|
||||
const {temp, h} = cells;
|
||||
const { cells, features } = grid;
|
||||
const { temp, h } = cells;
|
||||
Math.random = aleaPRNG(seed);
|
||||
|
||||
const ICEBERG_MAX_TEMP = 0;
|
||||
|
|
@ -432,7 +432,7 @@ function drawIce() {
|
|||
{
|
||||
const type = "iceShield";
|
||||
const getType = cellId => (h[cellId] >= 20 && temp[cellId] <= GLACIER_MAX_TEMP ? type : null);
|
||||
const isolines = getIsolines(grid, getType, {polygons: true});
|
||||
const isolines = getIsolines(grid, getType, { polygons: true });
|
||||
isolines[type]?.polygons?.forEach(points => {
|
||||
const clipped = clipPoly(points);
|
||||
ice.append("polygon").attr("points", clipped).attr("type", type);
|
||||
|
|
@ -476,11 +476,11 @@ function toggleCultures(event) {
|
|||
|
||||
function drawCultures() {
|
||||
TIME && console.time("drawCultures");
|
||||
const {cells, cultures} = pack;
|
||||
const { cells, cultures } = pack;
|
||||
|
||||
const bodyPaths = new Array(cultures.length - 1);
|
||||
const isolines = getIsolines(pack, cellId => cells.culture[cellId], {fill: true, waterGap: true});
|
||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
||||
const isolines = getIsolines(pack, cellId => cells.culture[cellId], { fill: true, waterGap: true });
|
||||
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||
const color = cultures[index].color;
|
||||
bodyPaths.push(getGappedFillPaths("culture", fill, waterGap, color, index));
|
||||
});
|
||||
|
|
@ -505,11 +505,11 @@ function toggleReligions(event) {
|
|||
|
||||
function drawReligions() {
|
||||
TIME && console.time("drawReligions");
|
||||
const {cells, religions} = pack;
|
||||
const { cells, religions } = pack;
|
||||
|
||||
const bodyPaths = new Array(religions.length - 1);
|
||||
const isolines = getIsolines(pack, cellId => cells.religion[cellId], {fill: true, waterGap: true});
|
||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
||||
const isolines = getIsolines(pack, cellId => cells.religion[cellId], { fill: true, waterGap: true });
|
||||
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||
const color = religions[index].color;
|
||||
bodyPaths.push(getGappedFillPaths("religion", fill, waterGap, color, index));
|
||||
});
|
||||
|
|
@ -533,7 +533,7 @@ function toggleStates(event) {
|
|||
|
||||
function drawStates() {
|
||||
TIME && console.time("drawStates");
|
||||
const {cells, states} = pack;
|
||||
const { cells, states } = pack;
|
||||
|
||||
const maxLength = states.length - 1;
|
||||
const bodyPaths = new Array(maxLength);
|
||||
|
|
@ -541,8 +541,8 @@ function drawStates() {
|
|||
const haloPaths = new Array(maxLength);
|
||||
|
||||
const renderHalo = shapeRendering.value === "geometricPrecision";
|
||||
const isolines = getIsolines(pack, cellId => cells.state[cellId], {fill: true, waterGap: true, halo: renderHalo});
|
||||
Object.entries(isolines).forEach(([index, {fill, waterGap, halo}]) => {
|
||||
const isolines = getIsolines(pack, cellId => cells.state[cellId], { fill: true, waterGap: true, halo: renderHalo });
|
||||
Object.entries(isolines).forEach(([index, { fill, waterGap, halo }]) => {
|
||||
const color = states[index].color;
|
||||
bodyPaths.push(getGappedFillPaths("state", fill, waterGap, color, index));
|
||||
|
||||
|
|
@ -588,11 +588,11 @@ function toggleProvinces(event) {
|
|||
|
||||
function drawProvinces() {
|
||||
TIME && console.time("drawProvinces");
|
||||
const {cells, provinces} = pack;
|
||||
const { cells, provinces } = pack;
|
||||
|
||||
const bodyPaths = new Array(provinces.length - 1);
|
||||
const isolines = getIsolines(pack, cellId => cells.province[cellId], {fill: true, waterGap: true});
|
||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
||||
const isolines = getIsolines(pack, cellId => cells.province[cellId], { fill: true, waterGap: true });
|
||||
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||
const color = provinces[index].color;
|
||||
bodyPaths.push(getGappedFillPaths("province", fill, waterGap, color, index));
|
||||
});
|
||||
|
|
@ -653,8 +653,134 @@ function drawGrid() {
|
|||
.attr("height", maxHeight)
|
||||
.attr("fill", "url(" + pattern + ")")
|
||||
.attr("stroke", "none");
|
||||
|
||||
// Add grid numbering if enabled
|
||||
const showNumbers = gridOverlay.attr("data-show-numbers") === "1";
|
||||
if (showNumbers) {
|
||||
drawGridNumbers(maxWidth, maxHeight, scale, dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
function drawGridNumbers(maxWidth, maxHeight, scale, dx, dy) {
|
||||
const gridType = gridOverlay.attr("type") || "pointyHex";
|
||||
const fontSize = gridOverlay.attr("data-number-size") || 8;
|
||||
const numberColor = gridOverlay.attr("data-number-color") || "#808080";
|
||||
|
||||
// Get cell dimensions based on grid type
|
||||
const cellDimensions = getGridCellDimensions(gridType);
|
||||
const cellWidth = cellDimensions.width * scale;
|
||||
const cellHeight = cellDimensions.height * scale;
|
||||
|
||||
// Calculate grid dimensions based on ACTUAL spacing used
|
||||
const rowSpacing = cellHeight * 0.5; // Same as used in getGridCellCenter
|
||||
const cols = Math.ceil(maxWidth / cellWidth) + 2; // Add extra to cover edges
|
||||
const rows = Math.ceil(maxHeight / rowSpacing) + 2; // Use rowSpacing, not cellHeight
|
||||
|
||||
// Create numbers group
|
||||
const numbersGroup = gridOverlay.append("g").attr("id", "gridNumbers");
|
||||
|
||||
let counter = 1;
|
||||
// Generate grid numbers for ALL cells (positioning is now perfect)
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const position = getGridCellCenter(gridType, col, row, cellWidth, cellHeight, dx, dy);
|
||||
|
||||
numbersGroup
|
||||
.append("text")
|
||||
.attr("x", position.x)
|
||||
.attr("y", position.y)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "middle")
|
||||
.attr("font-size", fontSize)
|
||||
.attr("fill", numberColor)
|
||||
.attr("pointer-events", "none")
|
||||
.text(String(counter).padStart(4, "0"));
|
||||
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getGridCellDimensions(gridType) {
|
||||
// Base dimensions from pattern definitions
|
||||
switch (gridType) {
|
||||
case "square":
|
||||
return { width: 25, height: 25 };
|
||||
case "pointyHex":
|
||||
return { width: 25, height: 43.4 };
|
||||
case "flatHex":
|
||||
return { width: 43.4, height: 25 };
|
||||
case "square45deg":
|
||||
return { width: 35.355, height: 35.355 };
|
||||
case "squareTruncated":
|
||||
case "squareTetrakis":
|
||||
return { width: 25, height: 25 };
|
||||
case "triangleHorizontal":
|
||||
return { width: 41.76, height: 72.33 };
|
||||
case "triangleVertical":
|
||||
return { width: 72.33, height: 41.76 };
|
||||
case "trihexagonal":
|
||||
return { width: 25, height: 43.4 };
|
||||
case "rhombille":
|
||||
return { width: 82.5, height: 50 };
|
||||
default:
|
||||
return { width: 25, height: 43.4 };
|
||||
}
|
||||
}
|
||||
|
||||
function getGridCellCenter(gridType, col, row, cellWidth, cellHeight, dx, dy) {
|
||||
let x, y;
|
||||
|
||||
if (gridType === "pointyHex") {
|
||||
// Pointy hex pattern: width=25, height=43.4
|
||||
// Hexagons interlock: each row is spaced at 3/4 height
|
||||
// Based on user's marker placement, need to adjust Y-center
|
||||
|
||||
// Vertical spacing adjusting based on visual alignment (not pure geometry)
|
||||
const rowSpacing = cellHeight * 0.5;
|
||||
|
||||
x = col * cellWidth;
|
||||
y = row * rowSpacing;
|
||||
|
||||
// Every other row (EVEN rows: 0, 2, 4...) is offset horizontally
|
||||
if (row % 2 === 0) {
|
||||
x += cellWidth / 2;
|
||||
}
|
||||
|
||||
// Center the number in the hexagon
|
||||
x += cellWidth / 2;
|
||||
// Top row is perfect at 0.35
|
||||
y += cellHeight * 0.35;
|
||||
|
||||
} else if (gridType === "flatHex") {
|
||||
// Flat hex grid: hexagons with flat sides up/down
|
||||
// Columns are horizontally compressed (overlap by 25%)
|
||||
x = col * (cellWidth * 0.75);
|
||||
y = row * cellHeight;
|
||||
|
||||
// Every other column is offset vertically by half height
|
||||
if (col % 2 === 1) {
|
||||
y += cellHeight / 2;
|
||||
}
|
||||
|
||||
// Center the number in the hexagon
|
||||
x += cellWidth / 2;
|
||||
y += cellHeight / 2;
|
||||
|
||||
} else {
|
||||
// Square and other regular grids - simple grid
|
||||
x = col * cellWidth + cellWidth / 2;
|
||||
y = row * cellHeight + cellHeight / 2;
|
||||
}
|
||||
|
||||
// Apply shift offsets
|
||||
x += parseFloat(dx) || 0;
|
||||
y += parseFloat(dy) || 0;
|
||||
|
||||
return { x: rn(x, 2), y: rn(y, 2) };
|
||||
}
|
||||
|
||||
|
||||
function toggleCoordinates(event) {
|
||||
if (!coordinates.selectAll("*").size()) {
|
||||
turnButtonOn("toggleCoordinates");
|
||||
|
|
@ -711,7 +837,7 @@ function drawCoordinates() {
|
|||
}
|
||||
}
|
||||
|
||||
return {x, y, text};
|
||||
return { x, y, text };
|
||||
});
|
||||
|
||||
const path = round(d3.geoPath(projection)(graticule()));
|
||||
|
|
@ -795,7 +921,7 @@ function drawRivers() {
|
|||
TIME && console.time("drawRivers");
|
||||
rivers.selectAll("*").remove();
|
||||
|
||||
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 (points && points.length !== cells.length) {
|
||||
|
|
@ -831,7 +957,7 @@ function drawRoutes() {
|
|||
const routePaths = {};
|
||||
|
||||
for (const route of pack.routes) {
|
||||
const {i, group, points} = route;
|
||||
const { i, group, points } = route;
|
||||
if (!points || points.length < 2) continue;
|
||||
if (!routePaths[group]) routePaths[group] = [];
|
||||
routePaths[group].push(`<path id="route${i}" d="${Routes.getPath(route)}"/>`);
|
||||
|
|
@ -943,12 +1069,12 @@ function drawZones() {
|
|||
const filterBy = byId("zonesFilterType").value;
|
||||
const isFiltered = filterBy && filterBy !== "all";
|
||||
const visibleZones = pack.zones.filter(
|
||||
({hidden, cells, type}) => !hidden && cells.length && (!isFiltered || type === filterBy)
|
||||
({ hidden, cells, type }) => !hidden && cells.length && (!isFiltered || type === filterBy)
|
||||
);
|
||||
zones.html(visibleZones.map(drawZone).join(""));
|
||||
}
|
||||
|
||||
function drawZone({i, cells, type, color}) {
|
||||
function drawZone({ i, cells, type, color }) {
|
||||
const path = getVertexPath(cells);
|
||||
return `<path id="zone${i}" data-id="${i}" data-type="${type}" d="${path}" fill="${color}" />`;
|
||||
}
|
||||
|
|
@ -1002,7 +1128,7 @@ function turnButtonOn(el) {
|
|||
}
|
||||
|
||||
// move layers on mapLayers dragging (jquery sortable)
|
||||
$("#mapLayers").sortable({items: "li:not(.solid)", containment: "parent", cancel: ".solid", update: moveLayer});
|
||||
$("#mapLayers").sortable({ items: "li:not(.solid)", containment: "parent", cancel: ".solid", update: moveLayer });
|
||||
function moveLayer(event, ui) {
|
||||
const el = getLayer(ui.item.attr("id"));
|
||||
if (!el) return;
|
||||
|
|
|
|||
1100
modules/ui/layers.js.bak_sed
Normal file
1100
modules/ui/layers.js.bak_sed
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -209,6 +209,9 @@ function selectStyleElement() {
|
|||
styleGridScale.value = el.attr("scale") || 1;
|
||||
styleGridShiftX.value = el.attr("dx") || 0;
|
||||
styleGridShiftY.value = el.attr("dy") || 0;
|
||||
styleGridShowNumbers.checked = el.attr("data-show-numbers") === "1";
|
||||
styleGridNumberSize.value = el.attr("data-number-size") || 8;
|
||||
styleGridNumberColor.value = styleGridNumberColorOutput.value = el.attr("data-number-color") || "#808080";
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
|
|
@ -515,6 +518,12 @@ styleGridType.on("change", function () {
|
|||
calculateFriendlyGridSize();
|
||||
});
|
||||
|
||||
// Grid numbering UI elements
|
||||
const styleGridShowNumbers = byId("styleGridShowNumbers");
|
||||
const styleGridNumberSize = byId("styleGridNumberSize");
|
||||
const styleGridNumberColor = byId("styleGridNumberColor");
|
||||
const styleGridNumberColorOutput = byId("styleGridNumberColorOutput");
|
||||
|
||||
styleGridScale.on("input", function () {
|
||||
getEl().attr("scale", this.value);
|
||||
if (layerIsOn("toggleGrid")) drawGrid();
|
||||
|
|
@ -537,6 +546,23 @@ styleGridShiftY.on("input", function () {
|
|||
if (layerIsOn("toggleGrid")) drawGrid();
|
||||
});
|
||||
|
||||
styleGridShowNumbers.on("change", function () {
|
||||
getEl().attr("data-show-numbers", this.checked ? "1" : "0");
|
||||
if (layerIsOn("toggleGrid")) drawGrid();
|
||||
});
|
||||
|
||||
styleGridNumberSize.on("input", function () {
|
||||
getEl().attr("data-number-size", this.value);
|
||||
if (layerIsOn("toggleGrid")) drawGrid();
|
||||
});
|
||||
|
||||
styleGridNumberColor.on("input", function () {
|
||||
styleGridNumberColorOutput.value = this.value;
|
||||
getEl().attr("data-number-color", this.value);
|
||||
if (layerIsOn("toggleGrid")) drawGrid();
|
||||
});
|
||||
|
||||
|
||||
styleRescaleMarkers.on("change", function () {
|
||||
markers.attr("rescale", +this.checked);
|
||||
invokeActiveZooming();
|
||||
|
|
@ -626,36 +652,36 @@ openCreateHeightmapSchemeButton.on("click", function () {
|
|||
|
||||
Array.from(container.querySelectorAll("input.stop")).forEach(
|
||||
(input, index) =>
|
||||
(input.oninput = function () {
|
||||
stops[index] = this.value;
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderGradient();
|
||||
})
|
||||
(input.oninput = function () {
|
||||
stops[index] = this.value;
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderGradient();
|
||||
})
|
||||
);
|
||||
|
||||
Array.from(container.querySelectorAll("button.remove")).forEach(
|
||||
button =>
|
||||
(button.onclick = function () {
|
||||
const index = +this.dataset.index;
|
||||
stops.splice(index, 1);
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderStops();
|
||||
renderGradient();
|
||||
})
|
||||
(button.onclick = function () {
|
||||
const index = +this.dataset.index;
|
||||
stops.splice(index, 1);
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderStops();
|
||||
renderGradient();
|
||||
})
|
||||
);
|
||||
|
||||
Array.from(container.querySelectorAll("button.add")).forEach(
|
||||
(button, index) =>
|
||||
(button.onclick = function () {
|
||||
const middleColor = d3.interpolateRgb(stops[index], stops[index + 1])(0.5);
|
||||
stops.splice(index + 1, 0, toHEX(middleColor));
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderStops();
|
||||
renderGradient();
|
||||
})
|
||||
(button.onclick = function () {
|
||||
const middleColor = d3.interpolateRgb(stops[index], stops[index + 1])(0.5);
|
||||
stops.splice(index + 1, 0, toHEX(middleColor));
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderStops();
|
||||
renderGradient();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -687,7 +713,7 @@ openCreateHeightmapSchemeButton.on("click", function () {
|
|||
Create: handleCreate,
|
||||
Cancel: handleClose
|
||||
},
|
||||
position: {my: "center top+150", at: "center top", of: "svg"}
|
||||
position: { my: "center top+150", at: "center top", of: "svg" }
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -800,7 +826,7 @@ styleFontAdd.on("click", function () {
|
|||
$("#addFontDialog").dialog({
|
||||
title: "Add custom font",
|
||||
width: "26em",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
position: { my: "center", at: "center", of: "svg" },
|
||||
buttons: {
|
||||
Add: function () {
|
||||
const family = addFontNameInput.value;
|
||||
|
|
@ -1098,7 +1124,7 @@ styleScaleBar.on("input", function (event) {
|
|||
const scaleBarBack = scaleBar.select("#scaleBarBack");
|
||||
if (!scaleBarBack.size()) return;
|
||||
|
||||
const {id, value} = event.target;
|
||||
const { id, value } = event.target;
|
||||
|
||||
if (id === "styleScaleBarSize") scaleBar.attr("data-bar-size", value);
|
||||
else if (id === "styleScaleBarFontSize") scaleBar.attr("font-size", value);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue