mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 12:01:23 +01:00
pull master
This commit is contained in:
commit
e6457c4e4e
25 changed files with 930 additions and 447 deletions
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
<strong>Figure ${figureNo}</strong>. ${title}
|
||||
</div>
|
||||
<div>
|
||||
<button data-tip="Download the chart in svg format (can open in browser or Inkscape)" class="icon-download"></button>
|
||||
<button data-tip="Download chart data as a text file (.csv)" class="icon-download"></button>
|
||||
<button data-tip="Download the chart in svg format (can open in browser or Inkscape)" class="icon-chart-bar"></button>
|
||||
<button data-tip="Remove the chart" class="icon-trash"></button>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl);
|
||||
const serialized =
|
||||
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + 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};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -93,7 +93,9 @@ function overviewBurgs() {
|
|||
data-type="${type}"
|
||||
>
|
||||
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false" />
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${
|
||||
b.name
|
||||
}" autocorrect="off" spellcheck="false" />
|
||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled />
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled />
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">
|
||||
|
|
@ -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"}"
|
||||
></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${
|
||||
b.port ? "" : " inactive"
|
||||
}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span class="locks pointer ${
|
||||
b.lock ? "icon-lock" : "icon-lock-open inactive"
|
||||
}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -125,8 +131,12 @@ function overviewBurgs() {
|
|||
body.querySelectorAll("div > input.burgName").forEach(el => el.addEventListener("input", changeBurgName));
|
||||
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomIntoBurg));
|
||||
body.querySelectorAll("div > select.stateCulture").forEach(el => el.addEventListener("change", changeBurgCulture));
|
||||
body.querySelectorAll("div > input.burgPopulation").forEach(el => el.addEventListener("change", changeBurgPopulation));
|
||||
body.querySelectorAll("div > span.icon-star-empty").forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||
body
|
||||
.querySelectorAll("div > input.burgPopulation")
|
||||
.forEach(el => el.addEventListener("change", changeBurgPopulation));
|
||||
body
|
||||
.querySelectorAll("div > span.icon-star-empty")
|
||||
.forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
||||
|
|
@ -137,7 +147,9 @@ function overviewBurgs() {
|
|||
|
||||
function getCultureOptions(culture) {
|
||||
let options = "";
|
||||
pack.cultures.filter(c => !c.removed).forEach(c => (options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`));
|
||||
pack.cultures
|
||||
.filter(c => !c.removed)
|
||||
.forEach(c => (options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
@ -228,7 +240,8 @@ function overviewBurgs() {
|
|||
|
||||
function triggerBurgRemove() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (pack.burgs[burg].capital) return tip("You cannot remove the capital. Please change the capital first", false, "error");
|
||||
if (pack.burgs[burg].capital)
|
||||
return tip("You cannot remove the capital. Please change the capital first", false, "error");
|
||||
|
||||
confirmationDialog({
|
||||
title: "Remove burg",
|
||||
|
|
@ -266,8 +279,10 @@ function overviewBurgs() {
|
|||
function addBurgOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[cell] < 20) return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||
if (pack.cells.burg[cell]) return tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
||||
if (pack.cells.h[cell] < 20)
|
||||
return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||
if (pack.cells.burg[cell])
|
||||
return tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
||||
|
||||
addBurg(point); // add new burg
|
||||
|
||||
|
|
@ -301,7 +316,19 @@ function overviewBurgs() {
|
|||
const capital = b.capital;
|
||||
const province = pack.cells.province[b.cell];
|
||||
const parent = province ? province + states.length - 1 : b.state;
|
||||
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
|
||||
return {
|
||||
id,
|
||||
i: b.i,
|
||||
state: b.state,
|
||||
culture: b.culture,
|
||||
province,
|
||||
parent,
|
||||
name: b.name,
|
||||
population,
|
||||
capital,
|
||||
x: b.x,
|
||||
y: b.y
|
||||
};
|
||||
});
|
||||
const data = states.concat(burgs);
|
||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||
|
|
@ -452,7 +479,7 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
function downloadBurgsData() {
|
||||
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Latitude,Longitude,Elevation (${heightUnit.value}),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town`; // headers
|
||||
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town`; // headers
|
||||
if (options.showMFCGMap) data += `,City Generator Link`;
|
||||
data += "\n";
|
||||
|
||||
|
|
@ -471,6 +498,8 @@ function overviewBurgs() {
|
|||
data += rn(b.population * populationRate * urbanization) + ",";
|
||||
|
||||
// add geography data
|
||||
data += b.x + ",";
|
||||
data += b.y + ",";
|
||||
data += getLatitude(b.y, 2) + ",";
|
||||
data += getLongitude(b.x, 2) + ",";
|
||||
data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
|
||||
|
|
|
|||
|
|
@ -44,7 +44,12 @@ function editEmblem(type, id, el) {
|
|||
|
||||
function defineEmblemData(e) {
|
||||
const parent = e.target.parentNode;
|
||||
const [g, t] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
const [g, t] =
|
||||
parent.id === "burgEmblems"
|
||||
? [pack.burgs, "burg"]
|
||||
: parent.id === "provinceEmblems"
|
||||
? [pack.provinces, "province"]
|
||||
: [pack.states, "state"];
|
||||
const i = +e.target.dataset.i;
|
||||
type = t;
|
||||
id = type + "COA" + i;
|
||||
|
|
@ -88,8 +93,12 @@ function editEmblem(type, id, el) {
|
|||
|
||||
emblemBurgs.options.length = 0;
|
||||
emblemBurgs.options.add(new Option("", 0, false, !burg));
|
||||
const burgList = validBurgs.filter(burg => (province ? pack.cells.province[burg.cell] === province : burg.state === state));
|
||||
burgList.forEach(b => emblemBurgs.options.add(new Option(b.capital ? "👑 " + b.name : b.name, b.i, false, b.i === burg)));
|
||||
const burgList = validBurgs.filter(burg =>
|
||||
province ? pack.cells.province[burg.cell] === province : burg.state === state
|
||||
);
|
||||
burgList.forEach(b =>
|
||||
emblemBurgs.options.add(new Option(b.capital ? "👑 " + b.name : b.name, b.i, false, b.i === burg))
|
||||
);
|
||||
emblemBurgs.options[0].disabled = true;
|
||||
|
||||
COArenderer.trigger(id, el.coa);
|
||||
|
|
@ -224,12 +233,15 @@ function editEmblem(type, id, el) {
|
|||
}
|
||||
|
||||
function upload(type) {
|
||||
const input = type === "image" ? document.getElementById("emblemImageToLoad") : document.getElementById("emblemSVGToLoad");
|
||||
const input =
|
||||
type === "image" ? document.getElementById("emblemImageToLoad") : document.getElementById("emblemSVGToLoad");
|
||||
const file = input.files[0];
|
||||
input.value = "";
|
||||
|
||||
if (file.size > 500000) {
|
||||
tip(`File is too big, please optimize file size up to 500kB and re-upload. Recommended size is 200x200 px and up to 100kB`, true, "error", 5000);
|
||||
const message =
|
||||
"File is too big, please optimize file size up to 500kB and re-upload. Recommended size is 200x200 px and up to 100kB";
|
||||
tip(message, true, "error", 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -238,36 +250,37 @@ function editEmblem(type, id, el) {
|
|||
reader.onload = function (readerEvent) {
|
||||
const result = readerEvent.target.result;
|
||||
const defs = document.getElementById("defs-emblems");
|
||||
const coa = document.getElementById(id); // old emblem
|
||||
const oldEmblem = document.getElementById(id);
|
||||
|
||||
if (type === "image") {
|
||||
const svg = `<svg id="${id}" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><image x="0" y="0" width="200" height="200" href="${result}"/></svg>`;
|
||||
defs.insertAdjacentHTML("beforeend", svg);
|
||||
} else {
|
||||
let href = result; // raster images
|
||||
if (type === "svg") {
|
||||
const el = document.createElement("html");
|
||||
el.innerHTML = result;
|
||||
|
||||
// remove sodipodi and inkscape attributes
|
||||
el.querySelectorAll("*").forEach(el => {
|
||||
const attributes = el.getAttributeNames();
|
||||
attributes.forEach(attr => {
|
||||
if (el.id === "adobe_illustrator_pgf") el.remove(); // remove Adobe Illustrator inner data
|
||||
|
||||
el.getAttributeNames().forEach(attr => {
|
||||
// remove sodipodi and inkscape attributes
|
||||
if (attr.includes("inkscape") || attr.includes("sodipodi")) el.removeAttribute(attr);
|
||||
});
|
||||
});
|
||||
|
||||
const svg = el.querySelector("svg");
|
||||
if (!svg) {
|
||||
tip("The file should be prepated for load to FMG. Please use Armoria or other relevant tools", false, "error");
|
||||
const message = "The file is not a valid SVG. Please use Armoria or other relevant tools";
|
||||
tip(message, false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const newEmblem = defs.appendChild(svg);
|
||||
newEmblem.id = id;
|
||||
newEmblem.setAttribute("width", 200);
|
||||
newEmblem.setAttribute("height", 200);
|
||||
const serialized = new XMLSerializer().serializeToString(svg);
|
||||
href = "data:image/svg+xml;base64," + window.btoa(serialized);
|
||||
}
|
||||
|
||||
if (coa) coa.remove(); // remove old emblem
|
||||
const svg = `<svg id="${id}" viewBox="0 0 200 200"><image width="200" height="200" href="${href}"/></svg>`;
|
||||
defs.insertAdjacentHTML("beforeend", svg);
|
||||
|
||||
if (oldEmblem) oldEmblem.remove();
|
||||
el.coa = "custom";
|
||||
emblemShapeSelector.disabled = true;
|
||||
};
|
||||
|
|
@ -351,7 +364,9 @@ function editEmblem(type, id, el) {
|
|||
validStates
|
||||
.map(state => {
|
||||
const el = document.getElementById("stateCOA" + state.i);
|
||||
return `<figure id="state_${state.i}"><a href="#provinces_${state.i}"><figcaption>${state.fullName}</figcaption>${getSVG(el, 200)}</a></figure>`;
|
||||
return `<figure id="state_${state.i}"><a href="#provinces_${state.i}"><figcaption>${
|
||||
state.fullName
|
||||
}</figcaption>${getSVG(el, 200)}</a></figure>`;
|
||||
})
|
||||
.join("") +
|
||||
`</div>`;
|
||||
|
|
@ -362,13 +377,14 @@ function editEmblem(type, id, el) {
|
|||
const figures = stateProvinces
|
||||
.map(province => {
|
||||
const el = document.getElementById("provinceCOA" + province.i);
|
||||
return `<figure id="province_${province.i}"><a href="#burgs_${province.i}"><figcaption>${province.fullName}</figcaption>${getSVG(
|
||||
el,
|
||||
200
|
||||
)}</a></figure>`;
|
||||
return `<figure id="province_${province.i}"><a href="#burgs_${province.i}"><figcaption>${
|
||||
province.fullName
|
||||
}</figcaption>${getSVG(el, 200)}</a></figure>`;
|
||||
})
|
||||
.join("");
|
||||
return stateProvinces.length ? `<div id="provinces_${state.i}">${back}<h2>${state.fullName} provinces</h2>${figures}</div>` : "";
|
||||
return stateProvinces.length
|
||||
? `<div id="provinces_${state.i}">${back}<h2>${state.fullName} provinces</h2>${figures}</div>`
|
||||
: "";
|
||||
})
|
||||
.join("");
|
||||
|
||||
|
|
@ -385,7 +401,9 @@ function editEmblem(type, id, el) {
|
|||
return `<figure id="burg_${burg.i}"><figcaption>${burg.name}</figcaption>${getSVG(el, 200)}</figure>`;
|
||||
})
|
||||
.join("");
|
||||
return provinceBurgs.length ? `<div id="burgs_${province.i}">${back}<h2>${province.fullName} burgs</h2>${provinceBurgFigures}</div>` : "";
|
||||
return provinceBurgs.length
|
||||
? `<div id="burgs_${province.i}">${back}<h2>${province.fullName} burgs</h2>${provinceBurgFigures}</div>`
|
||||
: "";
|
||||
})
|
||||
.join("");
|
||||
|
||||
|
|
@ -464,7 +482,7 @@ function editEmblem(type, id, el) {
|
|||
}
|
||||
div > a {
|
||||
float: right;
|
||||
font-family: monospace;
|
||||
font-family: var(--monospace);
|
||||
margin-top: 0.8em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -54,8 +54,12 @@ function overviewMarkers() {
|
|||
<div data-tip="Marker icon and type" style="width:12em">${icon} ${type}</div>
|
||||
<span style="padding-right:.1em" data-tip="Edit marker" class="icon-pencil"></span>
|
||||
<span style="padding-right:.1em" data-tip="Focus on marker position" class="icon-dot-circled pointer"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin ${pinned ? "" : "inactive"}" pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin ${
|
||||
pinned ? "" : "inactive"
|
||||
}" pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${
|
||||
lock ? "icon-lock" : "icon-lock-open inactive"
|
||||
}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove marker" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
})
|
||||
|
|
@ -170,16 +174,20 @@ function overviewMarkers() {
|
|||
}
|
||||
|
||||
function exportMarkers() {
|
||||
const headers = "Id,Type,Icon,Name,Note,X,Y\n";
|
||||
const headers = "Id,Type,Icon,Name,Note,X,Y,Latitude,Longitude\n";
|
||||
const quote = s => '"' + s.replaceAll('"', '""') + '"';
|
||||
|
||||
const body = pack.markers.map(marker => {
|
||||
const {i, type, icon, x, y} = marker;
|
||||
const id = `marker${i}`;
|
||||
const note = notes.find(note => note.id === id);
|
||||
const name = note ? quote(note.name) : 'Unknown';
|
||||
const legend = note ? quote(note.legend) : '';
|
||||
return [id, type, icon, name, legend, x, y].join(",");
|
||||
const name = note ? quote(note.name) : "Unknown";
|
||||
const legend = note ? quote(note.legend) : "";
|
||||
|
||||
const lat = getLatitude(y, 2);
|
||||
const lon = getLongitude(x, 2);
|
||||
|
||||
return [id, type, icon, name, legend, x, y, lat, lon].join(",");
|
||||
});
|
||||
|
||||
const data = headers + body.join("\n");
|
||||
|
|
|
|||
|
|
@ -23,23 +23,11 @@ function editNamesbase() {
|
|||
|
||||
const uploader = document.getElementById("namesbaseToLoad");
|
||||
document.getElementById("namesbaseUpload").addEventListener("click", () => {
|
||||
uploader.addEventListener(
|
||||
"change",
|
||||
function (event) {
|
||||
uploadFile(event.target, d => namesbaseUpload(d, true));
|
||||
},
|
||||
{once: true}
|
||||
);
|
||||
uploader.addEventListener("change", e => uploadFile(e.target, d => namesbaseUpload(d, true)), {once: true});
|
||||
uploader.click();
|
||||
});
|
||||
document.getElementById("namesbaseUploadExtend").addEventListener("click", () => {
|
||||
uploader.addEventListener(
|
||||
"change",
|
||||
function (event) {
|
||||
uploadFile(event.target, d => namesbaseUpload(d, false));
|
||||
},
|
||||
{once: true}
|
||||
);
|
||||
uploader.addEventListener("change", e => uploadFile(e.target, d => namesbaseUpload(d, false)), {once: true});
|
||||
uploader.click();
|
||||
});
|
||||
|
||||
|
|
@ -92,11 +80,13 @@ function editNamesbase() {
|
|||
|
||||
function updateNamesData() {
|
||||
const base = +document.getElementById("namesbaseSelect").value;
|
||||
const rawInput = document.getElementById("namesbaseTextarea").value;
|
||||
if (rawInput.split(",").length < 3) return tip("The names data provided is too short of incorrect", false, "error");
|
||||
const input = document.getElementById("namesbaseTextarea");
|
||||
if (input.value.split(",").length < 3)
|
||||
return tip("The names data provided is too short of incorrect", false, "error");
|
||||
|
||||
const namesData = rawInput.replace(/[/|]/g, "");
|
||||
nameBases[base].b = namesData;
|
||||
const securedNamesData = input.value.replace(/[/|]/g, "");
|
||||
nameBases[base].b = securedNamesData;
|
||||
input.value = securedNamesData;
|
||||
Names.updateChain(base);
|
||||
}
|
||||
|
||||
|
|
@ -113,19 +103,13 @@ function editNamesbase() {
|
|||
|
||||
function updateBaseMin() {
|
||||
const base = +document.getElementById("namesbaseSelect").value;
|
||||
if (+this.value > nameBases[base].max) {
|
||||
tip("Minimal length cannot be greater than maximal", false, "error");
|
||||
return;
|
||||
}
|
||||
if (+this.value > nameBases[base].max) return tip("Minimal length cannot be greater than maximal", false, "error");
|
||||
nameBases[base].min = +this.value;
|
||||
}
|
||||
|
||||
function updateBaseMax() {
|
||||
const base = +document.getElementById("namesbaseSelect").value;
|
||||
if (+this.value < nameBases[base].min) {
|
||||
tip("Maximal length should be greater than minimal", false, "error");
|
||||
return;
|
||||
}
|
||||
if (+this.value < nameBases[base].min) return tip("Maximal length should be greater than minimal", false, "error");
|
||||
nameBases[base].max = +this.value;
|
||||
}
|
||||
|
||||
|
|
@ -256,16 +240,15 @@ function editNamesbase() {
|
|||
|
||||
function namesbaseUpload(dataLoaded, override = true) {
|
||||
const data = dataLoaded.split("\r\n");
|
||||
if (!data || !data[0]) {
|
||||
tip("Cannot load a namesbase. Please check the data format", false, "error");
|
||||
return;
|
||||
}
|
||||
if (!data || !data[0]) return tip("Cannot load a namesbase. Please check the data format", false, "error");
|
||||
|
||||
Names.clearChains();
|
||||
if (override) nameBases = [];
|
||||
data.forEach(d => {
|
||||
const e = d.split("|");
|
||||
nameBases.push({name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b: e[5]});
|
||||
|
||||
data.forEach(base => {
|
||||
const [name, min, max, d, m, names] = base.split("|");
|
||||
const secureNames = names.replace(/[/|]/g, "");
|
||||
nameBases.push({name, min, max, d, m, b: secureNames});
|
||||
});
|
||||
|
||||
createBasesList();
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ document
|
|||
|
||||
// show popup with a list of Patreon supportes (updated manually)
|
||||
async function showSupporters() {
|
||||
const {supporters} = await import("../dynamic/supporters.js?v=19062022");
|
||||
const {supporters} = await import("../dynamic/supporters.js?v=1.89.15");
|
||||
const list = supporters.split("\n").sort();
|
||||
const columns = window.innerWidth < 800 ? 2 : 5;
|
||||
|
||||
|
|
@ -157,6 +157,8 @@ optionsContent.addEventListener("click", function (event) {
|
|||
else if (id === "translateExtent") toggleTranslateExtent(event.target);
|
||||
else if (id === "speakerTest") testSpeaker();
|
||||
else if (id === "themeColorRestore") restoreDefaultThemeColor();
|
||||
else if (id === "loadGoogleTranslateButton") loadGoogleTranslate();
|
||||
else if (id === "resetLanguage") resetLanguage();
|
||||
});
|
||||
|
||||
function mapSizeInputChange() {
|
||||
|
|
@ -474,6 +476,44 @@ function changeDialogsTheme(themeColor, transparency) {
|
|||
});
|
||||
}
|
||||
|
||||
function loadGoogleTranslate() {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://translate.google.com/translate_a/element.js?cb=initGoogleTranslate";
|
||||
script.onload = () => {
|
||||
document.getElementById("loadGoogleTranslateButton")?.remove();
|
||||
|
||||
// replace mapLayers underline <u> with bare text to avoid translation issue
|
||||
document
|
||||
.getElementById("mapLayers")
|
||||
.querySelectorAll("li")
|
||||
.forEach(el => {
|
||||
const text = el.innerHTML.replace(/<u>(.+)<\/u>/g, "$1");
|
||||
el.innerHTML = text;
|
||||
});
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function initGoogleTranslate() {
|
||||
new google.translate.TranslateElement(
|
||||
{pageLanguage: "en", layout: google.translate.TranslateElement.InlineLayout.VERTICAL},
|
||||
"google_translate_element"
|
||||
);
|
||||
}
|
||||
|
||||
function resetLanguage() {
|
||||
const languageSelect = document.querySelector("#google_translate_element select");
|
||||
if (!languageSelect.value) return;
|
||||
|
||||
languageSelect.value = "en";
|
||||
languageSelect.dispatchEvent(new Event("change"));
|
||||
|
||||
// do once again to actually reset the language
|
||||
languageSelect.value = "en";
|
||||
languageSelect.dispatchEvent(new Event("change"));
|
||||
}
|
||||
|
||||
function changeZoomExtent(value) {
|
||||
if (+zoomExtentMin.value > +zoomExtentMax.value) {
|
||||
[zoomExtentMin.value, zoomExtentMax.value] = [zoomExtentMax.value, zoomExtentMin.value];
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ function overviewRegiments(state) {
|
|||
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, " "));
|
||||
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
||||
insert(
|
||||
`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`
|
||||
);
|
||||
}
|
||||
header.querySelectorAll(".removable").forEach(function (e) {
|
||||
e.addEventListener("click", function () {
|
||||
|
|
@ -60,10 +62,12 @@ function overviewRegiments(state) {
|
|||
for (const r of s.military) {
|
||||
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" ");
|
||||
const lineData = options.military
|
||||
.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`)
|
||||
.map(
|
||||
u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`
|
||||
)
|
||||
.join(" ");
|
||||
|
||||
lines += /* html */ `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
||||
lines += /* html */ `<div class="states" data-id="${r.i}" data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
||||
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
|
||||
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
|
||||
|
|
@ -79,7 +83,9 @@ function overviewRegiments(state) {
|
|||
|
||||
lines += /* html */ `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
|
||||
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
|
||||
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`).join(" ")}
|
||||
${options.military
|
||||
.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`)
|
||||
.join(" ")}
|
||||
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
|
||||
</div>`;
|
||||
|
||||
|
|
@ -92,7 +98,9 @@ function overviewRegiments(state) {
|
|||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => regimentHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
|
||||
body
|
||||
.querySelectorAll("div.states")
|
||||
.forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
|
||||
}
|
||||
|
||||
function updateFilter(state) {
|
||||
|
|
@ -158,10 +166,7 @@ function overviewRegiments(state) {
|
|||
|
||||
function addRegimentOnClick() {
|
||||
const state = +regimentsFilter.value;
|
||||
if (state === -1) {
|
||||
tip("Please select state from the list", false, "error");
|
||||
return;
|
||||
}
|
||||
if (state === -1) return tip("Please select state from the list", false, "error");
|
||||
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
|
|
@ -180,15 +185,32 @@ function overviewRegiments(state) {
|
|||
|
||||
function downloadRegimentsData() {
|
||||
const units = options.military.map(u => u.name);
|
||||
let data = "State,Id,Name," + units.map(u => capitalize(u)).join(",") + ",Total\n"; // headers
|
||||
let data =
|
||||
"State,Id,Icon,Name," +
|
||||
units.map(u => capitalize(u)).join(",") +
|
||||
",X,Y,Latitude,Longitude,Base X,Base Y,Base Latitude,Base Longitude\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function (el) {
|
||||
data += el.dataset.state + ",";
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += units.map(u => el.dataset[u]).join(",") + ",";
|
||||
data += el.dataset.total + "\n";
|
||||
});
|
||||
for (const s of pack.states) {
|
||||
if (!s.i || s.removed || !s.military.length) continue;
|
||||
|
||||
for (const r of s.military) {
|
||||
data += s.name + ",";
|
||||
data += r.i + ",";
|
||||
data += r.icon + ",";
|
||||
data += r.name + ",";
|
||||
data += units.map(unit => r.u[unit]).join(",") + ",";
|
||||
|
||||
data += r.x + ",";
|
||||
data += r.y + ",";
|
||||
data += getLatitude(r.y, 2) + ",";
|
||||
data += getLongitude(r.x, 2) + ",";
|
||||
|
||||
data += r.bx + ",";
|
||||
data += r.by + ",";
|
||||
data += getLatitude(r.by, 2) + ",";
|
||||
data += getLongitude(r.bx, 2) + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
const name = getFileName("Regiments") + ".csv";
|
||||
downloadFile(data, name);
|
||||
|
|
|
|||
|
|
@ -137,7 +137,10 @@ function recalculatePopulation() {
|
|||
}
|
||||
|
||||
function regenerateStates() {
|
||||
recreateStates();
|
||||
const newStates = recreateStates();
|
||||
if (!newStates) return;
|
||||
|
||||
pack.states = newStates;
|
||||
BurgsAndStates.expandStates();
|
||||
BurgsAndStates.normalizeStates();
|
||||
BurgsAndStates.collectStatistics();
|
||||
|
|
@ -165,21 +168,32 @@ function recreateStates() {
|
|||
Math.random = aleaPRNG(localSeed);
|
||||
|
||||
const statesCount = +regionsOutput.value;
|
||||
if (!statesCount) {
|
||||
tip(`<i>States Number</i> option value is zero. No counties are generated`, false, "error");
|
||||
return null;
|
||||
}
|
||||
|
||||
const validBurgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
if (!validBurgs.length) {
|
||||
tip("There are no any burgs to generate states. Please create burgs first", false, "error");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!validBurgs.length)
|
||||
return tip("There are no any burgs to generate states. Please create burgs first", false, "error");
|
||||
if (validBurgs.length < statesCount)
|
||||
tip(
|
||||
`Not enough burgs to generate ${statesCount} states. Will generate only ${validBurgs.length} states`,
|
||||
false,
|
||||
"warn"
|
||||
);
|
||||
if (validBurgs.length < statesCount) {
|
||||
const message = `Not enough burgs to generate ${statesCount} states. Will generate only ${validBurgs.length} states`;
|
||||
tip(message, false, "warn");
|
||||
}
|
||||
|
||||
const lockedStates = pack.states.filter(s => s.i && !s.removed && s.lock);
|
||||
const validStates = pack.states.filter(s => s.i && !s.removed);
|
||||
const lockedStates = validStates.filter(s => s.lock);
|
||||
const lockedStatesIds = lockedStates.map(s => s.i);
|
||||
const lockedStatesCapitals = lockedStates.map(s => s.capital);
|
||||
|
||||
if (lockedStates.length === validStates.length) {
|
||||
tip("Unable to regenerate as all states are locked", false, "error");
|
||||
return null;
|
||||
}
|
||||
|
||||
// turn all old capitals into towns, except for the capitals of locked states
|
||||
for (const burg of validBurgs) {
|
||||
if (!burg.capital) continue;
|
||||
|
|
@ -229,7 +243,7 @@ function recreateStates() {
|
|||
// restore locked states
|
||||
lockedStates.forEach(state => {
|
||||
const newId = newStates.length;
|
||||
const {x, y} = validBurgs[state.capital];
|
||||
const {x, y} = pack.burgs[state.capital];
|
||||
capitalsTree.add([x, y]);
|
||||
|
||||
// update label id reference
|
||||
|
|
@ -300,9 +314,7 @@ function recreateStates() {
|
|||
newStates.push({i, name, type, capital: capital.i, center: capital.cell, culture, expansionism, coa});
|
||||
}
|
||||
|
||||
if (!statesCount) tip(`<i>States Number</i> option is set to zero. No counties are generated`, false, "warn");
|
||||
|
||||
pack.states = newStates;
|
||||
return newStates;
|
||||
}
|
||||
|
||||
function regenerateProvinces() {
|
||||
|
|
@ -920,6 +932,6 @@ function viewCellDetails() {
|
|||
}
|
||||
|
||||
async function overviewCharts() {
|
||||
const Overview = await import("../dynamic/overview/charts-overview.js?v=1.87.03");
|
||||
const Overview = await import("../dynamic/overview/charts-overview.js?v=1.89.24");
|
||||
Overview.open();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue