@@ -1707,18 +1690,6 @@
Generator settings:
@@ -2057,7 +2057,7 @@
>
Military
-
Ice
+
Ice
Markers
@@ -2257,7 +2257,8 @@
all supporters on Patreon!
-
diff --git a/libs/indexedDB.js b/libs/indexedDB.js
new file mode 100644
index 00000000..100b7be4
--- /dev/null
+++ b/libs/indexedDB.js
@@ -0,0 +1,72 @@
+let db;
+
+const DATABASE_NAME = "d2";
+const STORE_NAME = "s";
+const KEY_PATH = "key";
+
+const openDatabase = () => {
+ return new Promise((resolve, reject) => {
+ if (db) {
+ resolve();
+ } else {
+ const request = window.indexedDB.open(DATABASE_NAME, 1);
+
+ request.onsuccess = event => {
+ db = event.target.result;
+ resolve();
+ };
+
+ request.onerror = event => {
+ console.error("indexedDB request error");
+ console.log(event);
+ reject();
+ };
+
+ request.onupgradeneeded = event => {
+ db = event.target.result;
+ const objectStore = db.createObjectStore(STORE_NAME, {keyPath: KEY_PATH});
+ objectStore.transaction.oncomplete = () => {
+ db = event.target.result;
+ };
+ };
+ }
+ });
+};
+
+const ldb = {
+ get: key => {
+ return new Promise((resolve, reject) => {
+ if (!window.indexedDB) return reject("indexedDB not supported");
+
+ openDatabase().then(() => {
+ const hasStore = Array.from(db.objectStoreNames).includes(STORE_NAME);
+ if (!hasStore) return reject("no store found");
+
+ const transaction = db.transaction(STORE_NAME, "readonly");
+ const objectStore = transaction.objectStore(STORE_NAME);
+ const getRequest = objectStore.get(key);
+
+ getRequest.onsuccess = event => {
+ const result = event.target.result?.value || null;
+ resolve(result);
+ };
+ });
+ });
+ },
+
+ set: (key, value) => {
+ return new Promise((resolve, reject) => {
+ if (!window.indexedDB) return reject("indexedDB not supported");
+
+ openDatabase().then(() => {
+ const transaction = db.transaction(STORE_NAME, "readwrite");
+ const objectStore = transaction.objectStore(STORE_NAME);
+ const putRequest = objectStore.put({key, value});
+
+ putRequest.onsuccess = () => {
+ resolve();
+ };
+ });
+ });
+ }
+};
diff --git a/libs/jquery-ui.css b/libs/jquery-ui.css
index 94cc4790..beeb5e58 100644
--- a/libs/jquery-ui.css
+++ b/libs/jquery-ui.css
@@ -421,13 +421,13 @@ body .ui-dialog {
/* Component containers
----------------------------------*/
.ui-widget {
- font-family: Arial, Helvetica, sans-serif;
+ font-family: var(--sans-serif);
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
- font-family: Arial, Helvetica, sans-serif;
+ font-family: var(--sans-serif);
font-size: 1em;
}
.ui-widget button[class^="icon-"] {
diff --git a/main.js b/main.js
index a7756124..e6f95d45 100644
--- a/main.js
+++ b/main.js
@@ -29,7 +29,7 @@ if (PRODUCTION && "serviceWorker" in navigator) {
"beforeinstallprompt",
async event => {
event.preventDefault();
- const Installation = await import("./modules/dynamic/installation.js");
+ const Installation = await import("./modules/dynamic/installation.js?v=1.89.19");
Installation.init(event);
},
{once: true}
@@ -221,8 +221,7 @@ oceanLayers
document.addEventListener("DOMContentLoaded", async () => {
if (!location.hostname) {
const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
- alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the
instructions on how you can
- easily run a local web-server`;
+ alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the
instructions on how you can easily run a local web-server`;
$("#alert").dialog({
resizable: false,
@@ -240,6 +239,7 @@ document.addEventListener("DOMContentLoaded", async () => {
await checkLoadParameters();
}
restoreDefaultEvents(); // apply default viewbox events
+ initiateAutosave();
});
function hideLoading() {
@@ -280,36 +280,20 @@ async function checkLoadParameters() {
return;
}
- // open latest map if option is active and map is stored
- const loadLastMap = () =>
- new Promise((resolve, reject) => {
- ldb.get("lastMap", blob => {
- if (blob) {
- WARN && console.warn("Load last saved map");
- try {
- uploadMap(blob);
- resolve();
- } catch (error) {
- reject(error);
- }
- } else {
- reject("No map stored");
- }
- });
- });
-
- if (onloadMap.value === "saved") {
- try {
- await loadLastMap();
- } catch (error) {
- ERROR && console.error(error);
- WARN && console.warn("Cannot load stored map, random map to be generated");
- await generateMapOnLoad();
+ // check if there is a map saved to indexedDB
+ try {
+ const blob = await ldb.get("lastMap");
+ if (blob) {
+ WARN && console.warn("Loading last stored map");
+ uploadMap(blob);
+ return;
}
- } else {
- WARN && console.warn("Generate random map");
- await generateMapOnLoad();
+ } catch (error) {
+ console.error(error);
}
+
+ WARN && console.warn("Generate random map");
+ generateMapOnLoad();
}
async function generateMapOnLoad() {
@@ -909,7 +893,7 @@ function addLakesInDeepDepressions() {
TIME && console.timeEnd("addLakesInDeepDepressions");
}
-// near sea lakes usually get a lot of water inflow, most of them should brake threshold and flow out to sea (see Ancylus Lake)
+// near sea lakes usually get a lot of water inflow, most of them should break threshold and flow out to sea (see Ancylus Lake)
function openNearSeaLakes() {
if (byId("templateInput").value === "Atoll") return; // no need for Atolls
@@ -924,7 +908,7 @@ function openNearSeaLakes() {
if (features[lake].type !== "lake") continue; // not a lake cell
check_neighbours: for (const c of cells.c[i]) {
- if (cells.t[c] !== 1 || cells.h[c] > LIMIT) continue; // water cannot brake this
+ if (cells.t[c] !== 1 || cells.h[c] > LIMIT) continue; // water cannot break this
for (const n of cells.c[c]) {
const ocean = cells.f[n];
diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js
index 1c0e07a7..cb4f6d05 100644
--- a/modules/cultures-generator.js
+++ b/modules/cultures-generator.js
@@ -119,17 +119,17 @@ window.Cultures = (function () {
function selectCultures(culturesNumber) {
let def = getDefault(culturesNumber);
const cultures = [];
-
+
pack.cultures?.forEach(function (culture) {
if (culture.lock) cultures.push(culture);
});
-
+
if (!cultures.length) {
if (culturesNumber === def.length) return def;
if (def.every(d => d.odd === 1)) return def.splice(0, culturesNumber);
}
- for (let culture, rnd, i = 0; cultures.length < culturesNumber && def.length > 0;) {
+ for (let culture, rnd, i = 0; cultures.length < culturesNumber && def.length > 0; ) {
do {
rnd = rand(def.length - 1);
culture = def[rnd];
@@ -511,89 +511,97 @@ window.Cultures = (function () {
TIME && console.time("expandCultures");
const {cells, cultures} = pack;
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
+ const queue = new PriorityQueue({comparator: (a, b) => a.priority - b.priority});
const cost = [];
const neutralRate = byId("neutralRate")?.valueAsNumber || 1;
- const neutral = cells.i.length * 0.6 * neutralRate; // limit cost for culture growth
+ const maxExpansionCost = cells.i.length * 0.6 * neutralRate; // limit cost for culture growth
// remove culture from all cells except of locked
- for (const cellId of cells.i) {
- const culture = cultures[cells.culture[cellId]];
- if (culture.lock) continue;
- cells.culture[cellId] = 0;
+ const hasLocked = cultures.some(c => !c.removed && c.lock);
+ if (hasLocked) {
+ for (const cellId of cells.i) {
+ const culture = cultures[cells.culture[cellId]];
+ if (culture.lock) continue;
+ cells.culture[cellId] = 0;
+ }
+ } else {
+ cells.culture = new Uint16Array(cells.i.length);
}
for (const culture of cultures) {
- if (!culture.i || culture.removed) continue;
- queue.queue({e: culture.center, p: 0, c: culture.i});
+ if (!culture.i || culture.removed || culture.lock) continue;
+ queue.queue({cellId: culture.center, cultureId: culture.i, priority: 0});
}
while (queue.length) {
- const {e, p, c} = queue.dequeue();
- const {type} = pack.cultures[c];
+ const {cellId, priority, cultureId} = queue.dequeue();
+ const {type, expansionism} = cultures[cultureId];
- cells.c[e].forEach(e => {
- const culture = cells.culture[e];
- if (culture?.lock) return; // do not overwrite cell of locked culture
+ cells.c[cellId].forEach(neibCellId => {
+ if (hasLocked) {
+ const neibCultureId = cells.culture[neibCellId];
+ if (neibCultureId && cultures[neibCultureId].lock) return; // do not overwrite cell of locked culture
+ }
- const biome = cells.biome[e];
- const biomeCost = getBiomeCost(c, biome, type);
- const biomeChangeCost = biome === cells.biome[e] ? 0 : 20; // penalty on biome change
- const heightCost = getHeightCost(e, cells.h[e], type);
- const riverCost = getRiverCost(cells.r[e], e, type);
- const typeCost = getTypeCost(cells.t[e], type);
- const totalCost =
- p + (biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[c].expansionism;
+ const biome = cells.biome[neibCellId];
+ const biomeCost = getBiomeCost(cultureId, biome, type);
+ const biomeChangeCost = biome === cells.biome[neibCellId] ? 0 : 20; // penalty on biome change
+ const heightCost = getHeightCost(neibCellId, cells.h[neibCellId], type);
+ const riverCost = getRiverCost(cells.r[neibCellId], neibCellId, type);
+ const typeCost = getTypeCost(cells.t[neibCellId], type);
- if (totalCost > neutral) return;
+ const cellCost = (biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / expansionism;
+ const totalCost = priority + cellCost;
- if (!cost[e] || totalCost < cost[e]) {
- if (cells.s[e] > 0) cells.culture[e] = c; // assign culture to populated cell
- cost[e] = totalCost;
- queue.queue({e, p: totalCost, c});
+ if (totalCost > maxExpansionCost) return;
+
+ if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
+ if (cells.pop[neibCellId] > 0) cells.culture[neibCellId] = cultureId; // assign culture to populated cell
+ cost[neibCellId] = totalCost;
+ queue.queue({cellId: neibCellId, cultureId, priority: totalCost});
}
});
}
+ function getBiomeCost(c, biome, type) {
+ if (cells.biome[cultures[c].center] === biome) return 10; // tiny penalty for native biome
+ if (type === "Hunting") return biomesData.cost[biome] * 5; // non-native biome penalty for hunters
+ if (type === "Nomadic" && biome > 4 && biome < 10) return biomesData.cost[biome] * 10; // forest biome penalty for nomads
+ return biomesData.cost[biome] * 2; // general non-native biome penalty
+ }
+
+ function getHeightCost(i, h, type) {
+ const f = pack.features[cells.f[i]],
+ a = cells.area[i];
+ if (type === "Lake" && f.type === "lake") return 10; // no lake crossing penalty for Lake cultures
+ if (type === "Naval" && h < 20) return a * 2; // low sea/lake crossing penalty for Naval cultures
+ if (type === "Nomadic" && h < 20) return a * 50; // giant sea/lake crossing penalty for Nomads
+ if (h < 20) return a * 6; // general sea/lake crossing penalty
+ if (type === "Highland" && h < 44) return 3000; // giant penalty for highlanders on lowlands
+ if (type === "Highland" && h < 62) return 200; // giant penalty for highlanders on lowhills
+ if (type === "Highland") return 0; // no penalty for highlanders on highlands
+ if (h >= 67) return 200; // general mountains crossing penalty
+ if (h >= 44) return 30; // general hills crossing penalty
+ return 0;
+ }
+
+ function getRiverCost(riverId, cellId, type) {
+ if (type === "River") return riverId ? 0 : 100; // penalty for river cultures
+ if (!riverId) return 0; // no penalty for others if there is no river
+ return minmax(cells.fl[cellId] / 10, 20, 100); // river penalty from 20 to 100 based on flux
+ }
+
+ function getTypeCost(t, type) {
+ if (t === 1) return type === "Naval" || type === "Lake" ? 0 : type === "Nomadic" ? 60 : 20; // penalty for coastline
+ if (t === 2) return type === "Naval" || type === "Nomadic" ? 30 : 0; // low penalty for land level 2 for Navals and nomads
+ if (t !== -1) return type === "Naval" || type === "Lake" ? 100 : 0; // penalty for mainland for navals
+ return 0;
+ }
+
TIME && console.timeEnd("expandCultures");
};
- function getBiomeCost(c, biome, type) {
- if (cells.biome[pack.cultures[c].center] === biome) return 10; // tiny penalty for native biome
- if (type === "Hunting") return biomesData.cost[biome] * 5; // non-native biome penalty for hunters
- if (type === "Nomadic" && biome > 4 && biome < 10) return biomesData.cost[biome] * 10; // forest biome penalty for nomads
- return biomesData.cost[biome] * 2; // general non-native biome penalty
- }
-
- function getHeightCost(i, h, type) {
- const f = pack.features[cells.f[i]],
- a = cells.area[i];
- if (type === "Lake" && f.type === "lake") return 10; // no lake crossing penalty for Lake cultures
- if (type === "Naval" && h < 20) return a * 2; // low sea/lake crossing penalty for Naval cultures
- if (type === "Nomadic" && h < 20) return a * 50; // giant sea/lake crossing penalty for Nomads
- if (h < 20) return a * 6; // general sea/lake crossing penalty
- if (type === "Highland" && h < 44) return 3000; // giant penalty for highlanders on lowlands
- if (type === "Highland" && h < 62) return 200; // giant penalty for highlanders on lowhills
- if (type === "Highland") return 0; // no penalty for highlanders on highlands
- if (h >= 67) return 200; // general mountains crossing penalty
- if (h >= 44) return 30; // general hills crossing penalty
- return 0;
- }
-
- function getRiverCost(r, i, type) {
- if (type === "River") return r ? 0 : 100; // penalty for river cultures
- if (!r) return 0; // no penalty for others if there is no river
- return minmax(cells.fl[i] / 10, 20, 100); // river penalty from 20 to 100 based on flux
- }
-
- function getTypeCost(t, type) {
- if (t === 1) return type === "Naval" || type === "Lake" ? 0 : type === "Nomadic" ? 60 : 20; // penalty for coastline
- if (t === 2) return type === "Naval" || type === "Nomadic" ? 30 : 0; // low penalty for land level 2 for Navals and nomads
- if (t !== -1) return type === "Naval" || type === "Lake" ? 100 : 0; // penalty for mainland for navals
- return 0;
- }
-
const getRandomShield = function () {
const type = rw(COA.shields.types);
return rw(COA.shields[type]);
diff --git a/modules/dynamic/editors/cultures-editor.js b/modules/dynamic/editors/cultures-editor.js
index 5e66941d..fe44db45 100644
--- a/modules/dynamic/editors/cultures-editor.js
+++ b/modules/dynamic/editors/cultures-editor.js
@@ -670,13 +670,13 @@ async function showHierarchy() {
});
}
-function recalculateCultures(must) {
- if (!must && !culturesAutoChange.checked) return;
-
- Cultures.expand();
- drawCultures();
- pack.burgs.forEach(b => (b.culture = pack.cells.culture[b.cell]));
- refreshCulturesEditor();
+function recalculateCultures(force) {
+ if (force || culturesAutoChange.checked) {
+ Cultures.expand();
+ drawCultures();
+ pack.burgs.forEach(b => (b.culture = pack.cells.culture[b.cell]));
+ refreshCulturesEditor();
+ }
}
function enterCultureManualAssignent() {
diff --git a/modules/dynamic/installation.js b/modules/dynamic/installation.js
index a9f2f789..8ccb6c16 100644
--- a/modules/dynamic/installation.js
+++ b/modules/dynamic/installation.js
@@ -28,7 +28,7 @@ function createButton() {
button.innerHTML = "Install";
button.onclick = openDialog;
button.onmouseenter = () => tip("Install the Application");
- document.querySelector("body").appendChild(button);
+ document.getElementById("optionsContainer").appendChild(button);
return button;
}
diff --git a/modules/dynamic/overview/charts-overview.js b/modules/dynamic/overview/charts-overview.js
index 4d5e14fb..35a83b5e 100644
--- a/modules/dynamic/overview/charts-overview.js
+++ b/modules/dynamic/overview/charts-overview.js
@@ -426,19 +426,18 @@ function renderChart({id, entity, plotBy, groupBy, sorting, type}) {
})
.flat();
+ const sortedData = sortData(chartData, sorting);
const colors = getColors();
const {offset, formatX = formatTicks} = plotTypeMap[type];
- const $chart = createStackedBarChart(chartData, {sorting, colors, tooltip, offset, formatX});
- insertChart(id, $chart, title);
+ const $chart = createStackedBarChart(sortedData, {colors, tooltip, offset, formatX});
+ insertChart(id, sortedData, $chart, title);
byId("chartsOverview__charts").lastChild.scrollIntoView();
}
// based on observablehq.com/@d3/stacked-horizontal-bar-chart
-function createStackedBarChart(data, {sorting, colors, tooltip, offset, formatX}) {
- const sortedData = sortData(data, sorting);
-
+function createStackedBarChart(sortedData, {colors, tooltip, offset, formatX}) {
const X = sortedData.map(d => d.value);
const Y = sortedData.map(d => d.name);
const Z = sortedData.map(d => d.group);
@@ -568,7 +567,7 @@ function createStackedBarChart(data, {sorting, colors, tooltip, offset, formatX}
return svg.node();
}
-function insertChart(id, $chart, title) {
+function insertChart(id, sortedData, $chart, title) {
const $chartContainer = byId("chartsOverview__charts");
const $figure = document.createElement("figure");
@@ -580,7 +579,8 @@ function insertChart(id, $chart, title) {
Figure ${figureNo} . ${title}
-
+
+
`;
@@ -589,7 +589,14 @@ function insertChart(id, $chart, title) {
$figure.appendChild($caption);
$chartContainer.appendChild($figure);
- const downloadChart = () => {
+ const downloadChartData = () => {
+ const name = `${getFileName(title)}.csv`;
+ const headers = "Name,Group,Value\n";
+ const values = sortedData.map(({name, group, value}) => `${name},${group},${value}`).join("\n");
+ downloadFile(headers + values, name);
+ };
+
+ const downloadChartSvg = () => {
const name = `${getFileName(title)}.svg`;
downloadFile($chart.outerHTML, name);
};
@@ -600,7 +607,8 @@ function insertChart(id, $chart, title) {
updateDialogPosition();
};
- $figure.querySelector("button.icon-download").on("click", downloadChart);
+ $figure.querySelector("button.icon-download").on("click", downloadChartData);
+ $figure.querySelector("button.icon-chart-bar").on("click", downloadChartSvg);
$figure.querySelector("button.icon-trash").on("click", removeChart);
}
diff --git a/modules/dynamic/supporters.js b/modules/dynamic/supporters.js
index c83e10ec..977b6e63 100644
--- a/modules/dynamic/supporters.js
+++ b/modules/dynamic/supporters.js
@@ -478,4 +478,44 @@ Rei
Fondue
Paavi1
Wil Sisney
-David Patterson`;
+David Patterson
+L
+Justin Scheffers
+Commieboo
+Garrison Wood
+Emsiron
+Frosty
+John Joseph Adams
+The_Lone_Wanderer
+Andrew Stein
+Groonfish
+soup
+Bruno Haack Vilar
+Ian Burke
+Tentacle Shogun
+Andrew Chandler
+Fritz Wulfram
+Doom Chupacabra
+Zakharov
+Dylan Fox
+Alfred Piccioni
+Avery Vreeland
+Kennedy
+Zack Wolf
+Matjam
+Jeff Johnston
+Hunter Hawthorne
+Sunsette
+Travis Love
+Dakian Delomast
+Kyle
+Davis Walker
+Naomi
+Clément D
+Jake Herr
+ReV0LT
+Jack Dawson
+Queso y Libertad
+RadioJay21H
+NEO
+Crecs`;
diff --git a/modules/fonts.js b/modules/fonts.js
index 9e97d277..53419851 100644
--- a/modules/fonts.js
+++ b/modules/fonts.js
@@ -2,15 +2,19 @@
const fonts = [
{family: "Arial"},
- {family: "Times New Roman"},
- {family: "Georgia"},
- {family: "Garamond"},
- {family: "Lucida Sans Unicode"},
- {family: "Courier New"},
- {family: "Verdana"},
- {family: "Impact"},
+ {family: "Brush Script MT"},
+ {family: "Century Gothic"},
{family: "Comic Sans MS"},
+ {family: "Copperplate"},
+ {family: "Courier New"},
+ {family: "Garamond"},
+ {family: "Georgia"},
+ {family: "Herculanum"},
+ {family: "Impact"},
{family: "Papyrus"},
+ {family: "Party LET"},
+ {family: "Times New Roman"},
+ {family: "Verdana"},
{
family: "Almendra SC",
src: "url(https://fonts.gstatic.com/s/almendrasc/v13/Iure6Yx284eebowr7hbyTaZOrLQ.woff2)",
@@ -38,12 +42,14 @@ const fonts = [
{
family: "Architects Daughter",
src: "url(https://fonts.gstatic.com/s/architectsdaughter/v8/RXTgOOQ9AAtaVOHxx0IUBM3t7GjCYufj5TXV5VnA2p8.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Bitter",
src: "url(https://fonts.gstatic.com/s/bitter/v12/zfs6I-5mjWQ3nxqccMoL2A.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Caesar Dressing",
@@ -54,12 +60,14 @@ const fonts = [
{
family: "Cinzel",
src: "url(https://fonts.gstatic.com/s/cinzel/v7/zOdksD_UUTk1LJF9z4tURA.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Dancing Script",
src: "url(https://fonts.gstatic.com/s/dancingscript/v9/KGBfwabt0ZRLA5W1ywjowUHdOuSHeh0r6jGTOGdAKHA.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Faster One",
@@ -82,12 +90,14 @@ const fonts = [
{
family: "Gloria Hallelujah",
src: "url(https://fonts.gstatic.com/s/gloriahallelujah/v9/CA1k7SlXcY5kvI81M_R28cNDay8z-hHR7F16xrcXsJw.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Great Vibes",
src: "url(https://fonts.gstatic.com/s/greatvibes/v5/6q1c0ofG6NKsEhAc2eh-3Y4P5ICox8Kq3LLUNMylGO4.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Henny Penny",
@@ -98,7 +108,8 @@ const fonts = [
{
family: "IM Fell English",
src: "url(https://fonts.gstatic.com/s/imfellenglish/v7/xwIisCqGFi8pff-oa9uSVAkYLEKE0CJQa8tfZYc_plY.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Kelly Slab",
@@ -121,7 +132,8 @@ const fonts = [
{
family: "Kaushan Script",
src: "url(https://fonts.gstatic.com/s/kaushanscript/v6/qx1LSqts-NtiKcLw4N03IEd0sm1ffa_JvZxsF_BEwQk.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Macondo",
@@ -150,7 +162,8 @@ const fonts = [
{
family: "Montez",
src: "url(https://fonts.gstatic.com/s/montez/v8/aq8el3-0osHIcFK6bXAPkw.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Nova Script",
@@ -161,7 +174,8 @@ const fonts = [
{
family: "Orbitron",
src: "url(https://fonts.gstatic.com/s/orbitron/v9/HmnHiRzvcnQr8CjBje6GQvesZW2xOQ-xsNqO47m55DA.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Oregano",
@@ -184,12 +198,14 @@ const fonts = [
{
family: "Satisfy",
src: "url(https://fonts.gstatic.com/s/satisfy/v8/2OzALGYfHwQjkPYWELy-cw.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Shadows Into Light",
src: "url(https://fonts.gstatic.com/s/shadowsintolight/v7/clhLqOv7MXn459PTh0gXYFK2TSYBz0eNcHnp4YqE4Ts.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
},
{
family: "Tapestry",
@@ -218,7 +234,8 @@ const fonts = [
{
family: "Yellowtail",
src: "url(https://fonts.gstatic.com/s/yellowtail/v8/GcIHC9QEwVkrA19LJU1qlPk_vArhqVIZ0nv9q090hN8.woff2)",
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
+ unicodeRange:
+ "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215"
}
];
diff --git a/modules/io/export.js b/modules/io/export.js
index fded29db..8484744b 100644
--- a/modules/io/export.js
+++ b/modules/io/export.js
@@ -10,7 +10,12 @@ async function saveSVG() {
link.href = url;
link.click();
- tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
+ tip(
+ `${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`,
+ true,
+ "success",
+ 5000
+ );
TIME && console.timeEnd("saveSVG");
}
@@ -36,7 +41,12 @@ async function savePNG() {
window.setTimeout(function () {
canvas.remove();
window.URL.revokeObjectURL(link.href);
- tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
+ tip(
+ `${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`,
+ true,
+ "success",
+ 5000
+ );
}, 1000);
});
};
@@ -140,7 +150,15 @@ async function saveTiles() {
// parse map svg to object url
async function getMapURL(type, options = {}) {
- const {debug = false, globe = false, noLabels = false, noWater = false, noScaleBar = false, noIce = false, fullMap = false} = options;
+ const {
+ debug = false,
+ globe = false,
+ noLabels = false,
+ noWater = false,
+ noScaleBar = false,
+ noIce = false,
+ fullMap = false
+ } = options;
if (fullMap) drawScaleBar(1);
@@ -222,12 +240,14 @@ async function getMapURL(type, options = {}) {
if (location.hostname && cloneEl.getElementById("oceanicPattern")) {
const el = cloneEl.getElementById("oceanicPattern");
const url = el.getAttribute("href");
- await new Promise(resolve => {
- getBase64(url, base64 => {
- el.setAttribute("href", base64);
- resolve();
+ if (url) {
+ await new Promise(resolve => {
+ getBase64(url, base64 => {
+ el.setAttribute("href", base64);
+ resolve();
+ });
});
- });
+ }
}
// add relief icons
@@ -315,7 +335,8 @@ async function getMapURL(type, options = {}) {
clone.remove();
- const serialized = `` + new XMLSerializer().serializeToString(cloneEl);
+ const serialized =
+ `` + new XMLSerializer().serializeToString(cloneEl);
const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"});
const url = window.URL.createObjectURL(blob);
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
@@ -461,7 +482,7 @@ function saveGeoJSON_Markers() {
const coordinates = getCoordinates(x, y, 4);
const id = `marker${i}`;
const note = notes.find(note => note.id === id);
- const properties = {id, type, icon, ...note, size, fill, stroke};
+ const properties = {id, type, icon, x, y, ...note, size, fill, stroke};
return {type: "Feature", geometry: {type: "Point", coordinates}, properties};
});
diff --git a/modules/io/load.js b/modules/io/load.js
index cd1ed8c0..a3549279 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -1,15 +1,13 @@
"use strict";
// Functions to load and parse .map files
-function quickLoad() {
- ldb.get("lastMap", blob => {
- if (blob) {
- loadMapPrompt(blob);
- } else {
- tip("No map stored. Save map to storage first", true, "error", 2000);
- ERROR && console.error("No map stored");
- }
- });
+async function quickLoad() {
+ const blob = await ldb.get("lastMap");
+ if (blob) loadMapPrompt(blob);
+ else {
+ tip("No map stored. Save map to browser storage first", true, "error", 2000);
+ ERROR && console.error("No map stored");
+ }
}
async function loadFromDropbox() {
diff --git a/modules/io/save.js b/modules/io/save.js
index db303eff..8cdd5c03 100644
--- a/modules/io/save.js
+++ b/modules/io/save.js
@@ -3,8 +3,6 @@
// prepare map data for saving
function getMapData() {
- TIME && console.time("createMapData");
-
const date = new Date();
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
@@ -116,13 +114,13 @@ function getMapData() {
fonts,
markers
].join("\r\n");
- TIME && console.timeEnd("createMapData");
return mapData;
}
// Download .map file
function dowloadMap() {
- if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
+ if (customization)
+ return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
closeDialogs("#alert");
const mapData = getMapData();
@@ -137,7 +135,8 @@ function dowloadMap() {
}
async function saveToDropbox() {
- if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
+ if (customization)
+ return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
closeDialogs("#alert");
const mapData = getMapData();
const filename = getFileName() + ".map";
@@ -150,12 +149,36 @@ async function saveToDropbox() {
}
}
-function quickSave() {
- if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
+async function initiateAutosave() {
+ const MINUTE = 60000; // munite in milliseconds
+ let lastSavedAt = Date.now();
+
+ async function autosave() {
+ const timeoutMinutes = byId("autosaveIntervalOutput").valueAsNumber;
+ if (!timeoutMinutes) return;
+
+ const diffInMinutes = (Date.now() - lastSavedAt) / MINUTE;
+ if (diffInMinutes < timeoutMinutes) return;
+ if (customization) return tip("Autosave: map cannot be saved in edit mode", false, "warning", 2000);
+
+ tip("Autosave: saving map...", false, "warning", 3000);
+ const mapData = getMapData();
+ const blob = new Blob([mapData], {type: "text/plain"});
+ await ldb.set("lastMap", blob);
+ console.log("Autosaved at", new Date().toLocaleTimeString());
+ lastSavedAt = Date.now();
+ }
+
+ setInterval(autosave, MINUTE / 2);
+}
+
+async function quickSave() {
+ if (customization)
+ return tip("Map cannot be saved when edit mode is active, please exit the mode first", false, "error");
const mapData = getMapData();
const blob = new Blob([mapData], {type: "text/plain"});
- if (blob) ldb.set("lastMap", blob); // auto-save map
+ await ldb.set("lastMap", blob); // auto-save map
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
}
diff --git a/modules/markers-generator.js b/modules/markers-generator.js
index b42ad2b3..26d27825 100644
--- a/modules/markers-generator.js
+++ b/modules/markers-generator.js
@@ -24,6 +24,7 @@ window.Markers = (function () {
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: 100, multiplier: 1, list: listInns, add: addInn},
@@ -42,15 +43,19 @@ window.Markers = (function () {
{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: "circuses", icon: "🎪", min: 80, each: 1000, multiplier: 1, list: listCircuses, add: addCircuses},
- {type: "jousts", icon: "🤺", dx: 48, min: 5, each: 500, multiplier: 1, list: listJousts, add: addJousts},
- {type: "canoes", icon: "🛶", min: 1000, each: 2000, multiplier: 1, list: listCanoes, add: addCanoes},
- {type: "migration", icon: "🐗", min: 20, each: 1000, multiplier: 1, list: listMigrations, add: addMigrations},
- {type: "dances", icon: "💃🏽", min: 5, each: 60, multiplier: 1, list: listDances, add: addDances},
+ {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: addCaves},
+ {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: 1, each: 3000, multiplier: +isFantasy, list: listRifts, add: addRifts}
+ {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},
];
}
@@ -172,7 +177,7 @@ window.Markers = (function () {
}
function listHotSprings({cells}) {
- return cells.i.filter(i => !occupied[i] && cells.h[i] > 50);
+ return cells.i.filter(i => !occupied[i] && cells.h[i] > 50 && cells.culture[i]);
}
function addHotSpring(id, cell) {
@@ -180,12 +185,37 @@ window.Markers = (function () {
const proper = Names.getCulture(cells.culture[cell]);
const temp = convertTemperature(gauss(35, 15, 20, 100));
- const status = P(0.6) ? "geothermal" : P(0.4) ? "springwater" : "natural";
- notes.push({
- id,
- name: proper + " Hot Springs",
- legend: `A ${status} hot springs area. Average temperature: ${temp}.`
+ 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}) {
@@ -728,7 +758,7 @@ window.Markers = (function () {
}
function addSacredForest(id, cell) {
- const {cells, cultures, religions} = pack;
+ const {cells, religions} = pack;
const culture = cells.culture[cell];
const religion = cells.religion[cell];
@@ -743,7 +773,7 @@ window.Markers = (function () {
}
function addSacredPinery(id, cell) {
- const {cells, cultures, religions} = pack;
+ const {cells, religions} = pack;
const culture = cells.culture[cell];
const religion = cells.religion[cell];
@@ -766,7 +796,7 @@ window.Markers = (function () {
}
function addSacredPalmGrove(id, cell) {
- const {cells, cultures, religions} = pack;
+ const {cells, religions} = pack;
const culture = cells.culture[cell];
const religion = cells.religion[cell];
@@ -842,8 +872,8 @@ window.Markers = (function () {
}
function addPirates(id, cell) {
- const name = `Pirates`;
- const legend = `Pirate ships have been spotted in these waters.`;
+ const name = "Pirates";
+ const legend = "Pirate ships have been spotted in these waters.";
notes.push({id, name, legend});
}
@@ -869,7 +899,7 @@ window.Markers = (function () {
"Idol"
];
const scripts = {
- cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ",
+ cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ",
geez: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ",
coptic: "ⲲⲴⲶⲸⲺⲼⲾⳀⳁⳂⳃⳄⳆⳈⳊⳌⳎⳐⳒⳔⳖⳘⳚⳜⳞⳠⳢⳤ⳥⳧⳩⳪ⳫⳬⳭⳲ⳹⳾ ",
tibetan: "ༀ༁༂༃༄༅༆༇༈༉༊་༌༐༑༒༓༔༕༖༗༘༙༚༛༜༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿",
@@ -917,11 +947,25 @@ window.Markers = (function () {
notes.push({id, name, legend});
}
+ 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 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});
+ }
+
function listCircuses({cells}) {
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && pack.cells.road[i]);
}
- function addCircuses(id, cell) {
+ function addCircuse(id, cell) {
const adjectives = [
"Fantastical",
"Wonderous",
@@ -943,7 +987,7 @@ window.Markers = (function () {
return cells.i.filter(i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population > 20);
}
- function addJousts(id, cell) {
+ function addJoust(id, cell) {
const {cells, burgs} = pack;
const types = ["Joust", "Competition", "Melee", "Tournament", "Contest"];
const virtues = ["cunning", "might", "speed", "the greats", "acumen", "brutality"];
@@ -958,11 +1002,29 @@ window.Markers = (function () {
notes.push({id, name, legend});
}
+ 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;
+ if (!cells.burg[cell]) return;
+
+ const burgName = burgs[cells.burg[cell]].name;
+ const type = "Fair";
+
+ 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});
+ }
+
function listCanoes({cells}) {
return cells.i.filter(i => !occupied[i] && cells.r[i]);
}
- function addCanoes(id, cell) {
+ function addCanoe(id, cell) {
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
const name = `Minor Jetty`;
@@ -975,7 +1037,7 @@ window.Markers = (function () {
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] <= 2);
}
- function addMigrations(id, cell) {
+ function addMigration(id, cell) {
const animals = [
"Antelopes",
"Apes",
@@ -1053,7 +1115,10 @@ window.Markers = (function () {
"exhibition",
"carnival",
"festival",
- "jubilee"
+ "jubilee",
+ "celebration",
+ "gathering",
+ "fete"
];
const people = [
"great and the good",
@@ -1089,7 +1154,7 @@ window.Markers = (function () {
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
}
- function addCaves(id, cell) {
+ function addCave(id, cell) {
const {cells} = pack;
const formations = {
@@ -1144,7 +1209,7 @@ window.Markers = (function () {
return cells.i.filter(i => !occupied[i] && pack.cells.pop[i] <= 3 && biomesData.habitability[pack.cells.biome[i]]);
}
- function addRifts(id, cell) {
+ function addRift(id, cell) {
const types = ["Demonic", "Interdimensional", "Abyssal", "Cosmic", "Cataclysmic", "Subterranean", "Ancient"];
const descriptions = [
@@ -1161,5 +1226,49 @@ window.Markers = (function () {
notes.push({id, name, legend});
}
+ 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});
+ }
+
+ 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 toponym = Names.getCulture(cells.culture[cell]);
+ const type = rw({
+ Necropolis: 5,
+ Crypt: 2,
+ Tomb: 2,
+ Graveyard: 1,
+ Cemetery: 2,
+ Mausoleum: 1,
+ Sepulchre: 1
+ });
+
+ const name = `${toponym} ${type}`;
+ const legend = ra([
+ "A foreboding necropolis shrouded in perpetual darkness, where eerie whispers echo through the winding corridors and spectral guardians stand watch over the tombs of long-forgotten souls",
+ "A towering necropolis adorned with macabre sculptures and guarded by formidable undead sentinels. Its ancient halls house the remains of fallen heroes, entombed alongside their cherished relics",
+ "This ethereal necropolis seems suspended between the realms of the living and the dead. Wisps of mist dance around the tombstones, while haunting melodies linger in the air, commemorating the departed",
+ "Rising from the desolate landscape, this sinister necropolis is a testament to necromantic power. Its skeletal spires cast ominous shadows, concealing forbidden knowledge and arcane secrets",
+ "An eerie necropolis where nature intertwines with death. Overgrown tombstones are entwined by thorny vines, and mournful spirits wander among the fading petals of once-vibrant flowers",
+ "A labyrinthine necropolis where each step echoes with haunting murmurs. The walls are adorned with ancient runes, and restless spirits guide or hinder those who dare to delve into its depths",
+ "This cursed necropolis is veiled in perpetual twilight, perpetuating a sense of impending doom. Dark enchantments shroud the tombs, and the moans of anguished souls resound through its crumbling halls",
+ "A sprawling necropolis built within a labyrinthine network of catacombs. Its halls are lined with countless alcoves, each housing the remains of the departed, while the distant sound of rattling bones fills the air",
+ "A desolate necropolis where an eerie stillness reigns. Time seems frozen amidst the decaying mausoleums, and the silence is broken only by the whispers of the wind and the rustle of tattered banners",
+ "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});
+ }
+
return {add, generate, regenerate, getConfig, setConfig, deleteMarker};
})();
diff --git a/modules/religions-generator.js b/modules/religions-generator.js
index 0ebd226d..916259a2 100644
--- a/modules/religions-generator.js
+++ b/modules/religions-generator.js
@@ -32,12 +32,16 @@ window.Religions = (function () {
being: [
"Ancestor",
"Ancient",
+ "Avatar",
"Brother",
+ "Champion",
"Chief",
"Council",
"Creator",
"Deity",
+ "Divine One",
"Elder",
+ "Enlightened Being",
"Father",
"Forebear",
"Forefather",
@@ -45,17 +49,25 @@ window.Religions = (function () {
"God",
"Goddess",
"Guardian",
+ "Guide",
+ "Hierach",
"Lady",
"Lord",
"Maker",
"Master",
"Mother",
"Numen",
+ "Oracle",
"Overlord",
+ "Protector",
"Reaper",
"Ruler",
+ "Sage",
+ "Seer",
"Sister",
"Spirit",
+ "Supreme Being",
+ "Transcendent",
"Virgin"
],
animal: [
@@ -71,36 +83,47 @@ window.Religions = (function () {
"Camel",
"Cat",
"Centaur",
+ "Cerberus",
"Chimera",
"Cobra",
+ "Cockatrice",
"Crane",
"Crocodile",
"Crow",
"Cyclope",
"Deer",
"Dog",
+ "Direwolf",
+ "Drake",
"Dragon",
"Eagle",
+ "Elephant",
"Elk",
"Falcon",
"Fox",
"Goat",
"Goose",
+ "Gorgon",
+ "Gryphon",
"Hare",
"Hawk",
"Heron",
+ "Hippogriff",
"Horse",
"Hound",
"Hyena",
"Ibis",
"Jackal",
"Jaguar",
+ "Kitsune",
"Kraken",
"Lark",
"Leopard",
"Lion",
+ "Manticore",
"Mantis",
"Marten",
+ "Minotaur",
"Moose",
"Mule",
"Narwhal",
@@ -109,8 +132,10 @@ window.Religions = (function () {
"Panther",
"Pegasus",
"Phoenix",
+ "Python",
"Rat",
"Raven",
+ "Roc",
"Rook",
"Scorpion",
"Serpent",
@@ -129,7 +154,8 @@ window.Religions = (function () {
"Wolf",
"Wolverine",
"Worm",
- "Wyvern"
+ "Wyvern",
+ "Yeti"
],
adjective: [
"Aggressive",
@@ -146,6 +172,7 @@ window.Religions = (function () {
"Brutal",
"Burning",
"Calm",
+ "Celestial",
"Cheerful",
"Crazy",
"Cruel",
@@ -157,6 +184,10 @@ window.Religions = (function () {
"Divine",
"Dying",
"Eternal",
+ "Ethernal",
+ "Empyreal",
+ "Enigmatic",
+ "Enlightened",
"Evil",
"Explicit",
"Fair",
@@ -177,7 +208,9 @@ window.Religions = (function () {
"Honest",
"Huge",
"Hungry",
+ "Illustrious",
"Immutable",
+ "Ineffable",
"Infallible",
"Inherent",
"Last",
@@ -190,31 +223,46 @@ window.Religions = (function () {
"Main",
"Major",
"Marine",
+ "Mythical",
+ "Mystical",
"Naval",
"New",
+ "Noble",
"Old",
+ "Otherworldly",
"Patient",
"Peaceful",
"Pregnant",
"Prime",
"Proud",
"Pure",
+ "Radiant",
+ "Resplendent",
"Sacred",
+ "Sacrosanct",
"Sad",
"Scary",
"Secret",
"Selected",
+ "Serene",
"Severe",
"Silent",
"Sleeping",
"Slumbering",
+ "Sovereign",
"Strong",
"Sunny",
"Superior",
+ "Supernatural",
"Sustainable",
+ "Transcendent",
+ "Transcendental",
"Troubled",
+ "Unearthly",
+ "Unfathomable",
"Unhappy",
"Unknown",
+ "Unseen",
"Waking",
"Wild",
"Wise",
@@ -282,26 +330,61 @@ window.Religions = (function () {
"Black",
"Blue",
"Bright",
+ "Bronze",
"Brown",
+ "Coral",
+ "Crimson",
"Dark",
+ "Emerald",
"Golden",
"Green",
"Grey",
+ "Indigo",
+ "Lavender",
"Light",
+ "Magenta",
+ "Maroon",
"Orange",
"Pink",
+ "Plum",
"Purple",
"Red",
+ "Ruby",
+ "Sapphire",
+ "Teal",
+ "Turquoise",
"White",
"Yellow"
]
};
const forms = {
- Folk: {Shamanism: 2, Animism: 2, "Ancestor worship": 1, Polytheism: 2},
- Organized: {Polytheism: 5, Dualism: 1, Monotheism: 4, "Non-theism": 1},
- Cult: {Cult: 1, "Dark Cult": 1},
- Heresy: {Heresy: 1}
+ Folk: {
+ Shamanism: 4,
+ Animism: 4,
+ Polytheism: 4,
+ Totemism: 2,
+ Druidism: 1,
+ "Ancestor Worship": 1,
+ "Nature Worship": 1
+ },
+ Organized: {
+ Polytheism: 14,
+ Monotheism: 12,
+ Dualism: 6,
+ Pantheism: 6,
+ "Non-theism": 4,
+ Henotheism: 1,
+ Panentheism: 1
+ },
+ Cult: {
+ Cult: 2,
+ "Dark Cult": 2,
+ Sect: 1
+ },
+ Heresy: {
+ Heresy: 1
+ }
};
const namingMethods = {
diff --git a/modules/ui/burgs-overview.js b/modules/ui/burgs-overview.js
index bf819465..5920c442 100644
--- a/modules/ui/burgs-overview.js
+++ b/modules/ui/burgs-overview.js
@@ -93,7 +93,9 @@ function overviewBurgs() {
data-type="${type}"
>
-
+
@@ -106,10 +108,14 @@ function overviewBurgs() {
data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}"
class="icon-star-empty${b.capital ? "" : " inactive pointer"}"
>
-
+