diff --git a/package-lock.json b/package-lock.json index 53616e24..25a3c267 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1353,7 +1353,6 @@ "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1394,7 +1393,6 @@ "integrity": "sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/browser": "4.0.18", "@vitest/mocker": "4.0.18", @@ -1876,7 +1874,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -2163,7 +2160,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2475,7 +2471,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -2551,7 +2546,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", diff --git a/public/modules/io/export.js b/public/modules/io/export.js index 51164c73..e2ab8263 100644 --- a/public/modules/io/export.js +++ b/public/modules/io/export.js @@ -574,3 +574,121 @@ function saveGeoJsonMarkers() { const fileName = getFileName("Markers") + ".geojson"; downloadFile(JSON.stringify(json), fileName, "application/json"); } + +function saveGeoJsonZones() { + const {zones, cells, vertices} = pack; + const json = {type: "FeatureCollection", features: []}; + + // Helper function to convert zone cells to polygon coordinates + // Handles multiple disconnected components and holes properly + function getZonePolygonCoordinates(zoneCells) { + const cellsInZone = new Set(zoneCells); + const ofSameType = (cellId) => cellsInZone.has(cellId); + const ofDifferentType = (cellId) => !cellsInZone.has(cellId); + + const checkedCells = new Set(); + const rings = []; // Array of LinearRings (each ring is an array of coordinates) + + // Find all boundary components by tracing each connected region + for (const cellId of zoneCells) { + if (checkedCells.has(cellId)) continue; + + // Check if this cell is on the boundary (has a neighbor outside the zone) + const neighbors = cells.c[cellId]; + const onBorder = neighbors.some(ofDifferentType); + if (!onBorder) continue; + + // Check if this is an inner lake (hole) - skip if so + const feature = pack.features[cells.f[cellId]]; + if (feature.type === "lake" && feature.shoreline) { + if (feature.shoreline.every(ofSameType)) continue; + } + + // Find a starting vertex that's on the boundary + const cellVertices = cells.v[cellId]; + let startingVertex = null; + + for (const vertexId of cellVertices) { + const vertexCells = vertices.c[vertexId]; + if (vertexCells.some(ofDifferentType)) { + startingVertex = vertexId; + break; + } + } + + if (startingVertex === null) continue; + + // Use connectVertices to trace the boundary (reusing existing logic) + const vertexChain = connectVertices({ + vertices, + startingVertex, + ofSameType, + addToChecked: (cellId) => checkedCells.add(cellId), + closeRing: false, // We'll close it manually after converting to coordinates + }); + + if (vertexChain.length < 3) continue; + + // Convert vertex chain to coordinates + const coordinates = []; + for (const vertexId of vertexChain) { + const [x, y] = vertices.p[vertexId]; + coordinates.push(getCoordinates(x, y, 4)); + } + + // Close the ring (first coordinate = last coordinate) + if (coordinates.length > 0) { + coordinates.push(coordinates[0]); + } + + // Only add ring if it has at least 4 positions (minimum for valid LinearRing) + if (coordinates.length >= 4) { + rings.push(coordinates); + } + } + + return rings; + } + + // Filter and process zones + zones.forEach(zone => { + // Exclude hidden zones and zones with no cells + if (zone.hidden || !zone.cells || zone.cells.length === 0) return; + + const rings = getZonePolygonCoordinates(zone.cells); + + // Skip if no valid rings were generated + if (rings.length === 0) return; + + const properties = { + id: zone.i, + name: zone.name, + type: zone.type, + color: zone.color, + cells: zone.cells + }; + + // If there's only one ring, use Polygon geometry + if (rings.length === 1) { + const feature = { + type: "Feature", + geometry: {type: "Polygon", coordinates: rings}, + properties + }; + json.features.push(feature); + } else { + // Multiple disconnected components: use MultiPolygon + // Each component is wrapped in its own array + const multiPolygonCoordinates = rings.map(ring => [ring]); + const feature = { + type: "Feature", + geometry: {type: "MultiPolygon", coordinates: multiPolygonCoordinates}, + properties + }; + json.features.push(feature); + } + }); + + const fileName = getFileName("Zones") + ".geojson"; + downloadFile(JSON.stringify(json), fileName, "application/json"); +} diff --git a/public/modules/markers-generator.js b/public/modules/markers-generator.js deleted file mode 100644 index 6ad7284e..00000000 --- a/public/modules/markers-generator.js +++ /dev/null @@ -1,1287 +0,0 @@ -"use strict"; - -window.Markers = (function () { - let config = getDefaultConfig(); - let occupied = []; - - function getDefaultConfig() { - const culturesSet = document.getElementById("culturesSet").value; - const isFantasy = culturesSet.includes("Fantasy"); - - /* - Default markers config: - type - short description (snake-case) - icon - unicode character or url to image - 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 - each: how many of the candidates should be added as markers - multiplier: multiply markers quantity to add - list: function to select candidates - add: function to add marker legend - */ - // 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}, - ]; - } - - const getConfig = () => config; - - const setConfig = newConfig => { - config = newConfig; - }; - - const generate = function () { - setConfig(getDefaultConfig()); - pack.markers = []; - generateTypes(); - }; - - const regenerate = () => { - pack.markers = pack.markers.filter(({i, lock, cell}) => { - if (lock) { - occupied[cell] = true; - return true; - } - const id = `marker${i}`; - document.getElementById(id)?.remove(); - const index = notes.findIndex(note => note.id === id); - if (index != -1) notes.splice(index, 1); - return false; - }); - - generateTypes(); - }; - - const add = marker => { - const base = config.find(c => c.type === marker.type); - if (base) { - const {icon, type, dx, dy, px} = base; - marker = addMarker({icon, type, dx, dy, px}, marker); - base.add("marker" + marker.i, marker.cell); - return marker; - } - - const i = last(pack.markers)?.i + 1 || 0; - pack.markers.push({...marker, i}); - occupied[marker.cell] = true; - return {...marker, i}; - }; - - function generateTypes() { - TIME && console.time("addMarkers"); - - config.forEach(({type, icon, dx, dy, px, min, each, multiplier, list, add}) => { - if (multiplier === 0) return; - - let candidates = Array.from(list(pack)); - let quantity = getQuantity(candidates, min, each, multiplier); - // uncomment for debugging: - // console.info(`${icon} ${type}: each ${each} of ${candidates.length}, min ${min} candidates. Got ${quantity}`); - - while (quantity && candidates.length) { - const [cell] = extractAnyElement(candidates); - const marker = addMarker({icon, type, dx, dy, px}, {cell}); - if (!marker) continue; - add("marker" + marker.i, cell); - quantity--; - } - }); - - occupied = []; - TIME && console.timeEnd("addMarkers"); - } - - function getQuantity(array, min, each, multiplier) { - if (!array.length || array.length < min / multiplier) return 0; - const requestQty = Math.ceil((array.length / each) * multiplier); - return array.length < requestQty ? array.length : requestQty; - } - - function extractAnyElement(array) { - const index = Math.floor(Math.random() * array.length); - return array.splice(index, 1); - } - - function getMarkerCoordinates(cell) { - const {cells, burgs} = pack; - const burgId = cells.burg[cell]; - - if (burgId) { - const {x, y} = burgs[burgId]; - return [x, y]; - } - - return cells.p[cell]; - } - - function addMarker(base, marker) { - 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}; - pack.markers.push(marker); - occupied[marker.cell] = true; - return marker; - } - - function deleteMarker(markerId) { - const noteId = "marker" + markerId; - notes = notes.filter(note => note.id !== noteId); - pack.markers = pack.markers.filter(m => m.i !== markerId); - } - - function listVolcanoes({cells}) { - return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70); - } - - function addVolcano(id, cell) { - 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])}.`}); - } - - 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 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}); - } - - 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 type = rw({ - "Healing Spring": 5, - "Purifying Well": 2, - "Enchanted Reservoir": 1, - "Creek of Luck": 1, - "Fountain of Youth": 1, - "Wisdom Spring": 1, - "Spring of Life": 1, - "Spring of Youth": 1, - "Healing Stream": 1 - }); - - const proper = Names.getCulture(cells.culture[cell]); - const name = `${proper} ${type}`; - 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}); - } - - 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 resource = rw(resources); - const burg = pack.burgs[cells.burg[cell]]; - const name = `${burg.name} — ${resource} mining town`; - const population = rn(burg.population * populationRate * urbanization); - const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine.`; - notes.push({id, name, legend}); - } - - function listBridges({cells, burgs}) { - const meanFlux = d3.mean(cells.fl.filter(fl => fl)); - return cells.i.filter( - i => - !occupied[i] && - cells.burg[i] && - cells.t[i] !== 1 && - burgs[cells.burg[i]].population > 20 && - cells.r[i] && - cells.fl[i] > meanFlux - ); - } - - function addBridge(id, cell) { - const {cells} = pack; - - 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} Bridge` : `${burg.name} Bridge`; - const weightedAdjectives = { - stone: 10, - wooden: 1, - lengthy: 2, - formidable: 2, - rickety: 1, - beaten: 1, - weathered: 1 - }; - const barriers = [ - "its collapse during the flood", - "being rumoured to attract trolls", - "the drying up of local trade", - "banditry infested the area", - "the old waypoints crumbled" - ]; - const legend = P(0.7) - ? `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}); - } - - function listInns({cells}) { - const crossRoads = cells.i.filter(i => !occupied[i] && cells.pop[i] > 5 && Routes.isCrossroad(i)); - return crossRoads; - } - - function addInn(id, cell) { - const colors = [ - "Dark", - "Light", - "Bright", - "Golden", - "White", - "Black", - "Red", - "Pink", - "Purple", - "Blue", - "Green", - "Yellow", - "Amber", - "Orange", - "Brown", - "Grey" - ]; - const animals = [ - "Antelope", - "Ape", - "Badger", - "Bear", - "Beaver", - "Bison", - "Boar", - "Buffalo", - "Cat", - "Crane", - "Crocodile", - "Crow", - "Deer", - "Dog", - "Eagle", - "Elk", - "Fox", - "Goat", - "Goose", - "Hare", - "Hawk", - "Heron", - "Horse", - "Hyena", - "Ibis", - "Jackal", - "Jaguar", - "Lark", - "Leopard", - "Lion", - "Mantis", - "Marten", - "Moose", - "Mule", - "Narwhal", - "Owl", - "Panther", - "Rat", - "Raven", - "Rook", - "Scorpion", - "Shark", - "Sheep", - "Snake", - "Spider", - "Swan", - "Tiger", - "Turtle", - "Wolf", - "Wolverine", - "Camel", - "Falcon", - "Hound", - "Ox" - ]; - const adjectives = [ - "New", - "Good", - "High", - "Old", - "Great", - "Big", - "Major", - "Happy", - "Main", - "Huge", - "Far", - "Beautiful", - "Fair", - "Prime", - "Ancient", - "Golden", - "Proud", - "Lucky", - "Fat", - "Honest", - "Giant", - "Distant", - "Friendly", - "Loud", - "Hungry", - "Magical", - "Superior", - "Peaceful", - "Frozen", - "Divine", - "Favorable", - "Brave", - "Sunny", - "Flying" - ]; - const methods = [ - "Boiled", - "Grilled", - "Roasted", - "Spit-roasted", - "Stewed", - "Stuffed", - "Jugged", - "Mashed", - "Baked", - "Braised", - "Poached", - "Marinated", - "Pickled", - "Smoked", - "Dried", - "Dry-aged", - "Corned", - "Fried", - "Pan-fried", - "Deep-fried", - "Dressed", - "Steamed", - "Cured", - "Syrupped", - "Flame-Broiled" - ]; - const courses = [ - "beef", - "pork", - "bacon", - "chicken", - "lamb", - "chevon", - "hare", - "rabbit", - "hart", - "deer", - "antlers", - "bear", - "buffalo", - "badger", - "beaver", - "turkey", - "pheasant", - "duck", - "goose", - "teal", - "quail", - "pigeon", - "seal", - "carp", - "bass", - "pike", - "catfish", - "sturgeon", - "escallop", - "pie", - "cake", - "pottage", - "pudding", - "onions", - "carrot", - "potato", - "beet", - "garlic", - "cabbage", - "eggplant", - "eggs", - "broccoli", - "zucchini", - "pepper", - "olives", - "pumpkin", - "spinach", - "peas", - "chickpea", - "beans", - "rice", - "pasta", - "bread", - "apples", - "peaches", - "pears", - "melon", - "oranges", - "mango", - "tomatoes", - "cheese", - "corn", - "rat tails", - "pig ears" - ]; - const types = [ - "hot", - "cold", - "fire", - "ice", - "smoky", - "misty", - "shiny", - "sweet", - "bitter", - "salty", - "sour", - "sparkling", - "smelly" - ]; - const drinks = [ - "wine", - "brandy", - "gin", - "whisky", - "rom", - "beer", - "cider", - "mead", - "liquor", - "spirits", - "vodka", - "tequila", - "absinthe", - "nectar", - "milk", - "kvass", - "kumis", - "tea", - "water", - "juice", - "sap" - ]; - - const typeName = P(0.3) ? "inn" : "tavern"; - const isAnimalThemed = P(0.7); - const animal = ra(animals); - const name = isAnimalThemed - ? P(0.6) - ? ra(colors) + " " + animal - : ra(adjectives) + " " + animal - : ra(adjectives) + " " + capitalize(typeName); - const meal = isAnimalThemed && P(0.3) ? animal : ra(courses); - const course = `${ra(methods)} ${meal}`.toLowerCase(); - const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase(); - const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here.`; - notes.push({id, name: "The " + name, legend}); - } - - function 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 proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); - notes.push({ - id, - name: getAdjective(proper) + " Lighthouse" + name, - legend: `A lighthouse to serve as a beacon for ships in the open sea.` - }); - } - - function listWaterfalls({cells}) { - return cells.i.filter( - i => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some(c => cells.h[c] < 40 && cells.r[c]) - ); - } - - function addWaterfall(id, cell) { - const {cells} = pack; - - const descriptions = [ - "A gorgeous waterfall flows here.", - "The rapids of an exceptionally beautiful waterfall.", - "An impressive waterfall has cut through the land.", - "The cascades of a stunning waterfall.", - "A river drops down from a great height forming a wonderous waterfall.", - "A breathtaking waterfall cuts through the landscape." - ]; - - 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 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 state = states[cells.state[cell]]; - if (!state.campaigns) state.campaigns = States.generateCampaign(state); - const campaign = ra(state.campaigns); - const date = generateDate(campaign.start, campaign.end); - const name = Names.getCulture(cells.culture[cell]) + " Battlefield"; - const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}.`; - notes.push({id, name, legend}); - } - - function 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 = `
Join our Discord server and Reddit community to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.
diff --git a/src/index.html b/src/index.html index 10cf62bd..cbb99440 100644 --- a/src/index.html +++ b/src/index.html @@ -6152,6 +6152,7 @@ +GeoJSON format is used in GIS tools such as QGIS. Check out @@ -8494,13 +8495,11 @@ - - @@ -8512,7 +8511,7 @@ - + @@ -8554,6 +8553,6 @@ - +