mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 12:01:23 +01:00
Merge remote-tracking branch 'origin/master' into localization-dev
This commit is contained in:
commit
60bbf2e487
292 changed files with 11868 additions and 7469 deletions
|
|
@ -252,13 +252,15 @@ window.BurgsAndStates = (function () {
|
|||
.filter(b => (newburg ? b.i == newburg.i : b.i && !b.removed))
|
||||
.forEach(b => {
|
||||
const pop = b.population;
|
||||
b.citadel = b.capital || (pop > 50 && P(0.75)) || P(0.5) ? 1 : 0;
|
||||
b.plaza = pop > 50 || (pop > 30 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.25) ? 1 : 0;
|
||||
b.walls = b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.2) ? 1 : 0;
|
||||
b.shanty = pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4)) ? 1 : 0;
|
||||
b.citadel = Number(b.capital || (pop > 50 && P(0.75)) || (pop > 15 && P(0.5)) || P(0.1));
|
||||
b.plaza = Number(pop > 20 || (pop > 10 && P(0.8)) || (pop > 4 && P(0.7)) || P(0.6));
|
||||
b.walls = Number(b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.1));
|
||||
b.shanty = Number(pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4)));
|
||||
const religion = cells.religion[b.cell];
|
||||
const theocracy = pack.states[b.state].form === "Theocracy";
|
||||
b.temple = (religion && theocracy) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5)) ? 1 : 0;
|
||||
b.temple = Number(
|
||||
(religion && theocracy && P(0.5)) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5))
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -859,9 +861,10 @@ window.BurgsAndStates = (function () {
|
|||
if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals
|
||||
}
|
||||
|
||||
if (base === 16 && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic
|
||||
if (base === 31 && (form === "Empire" || form === "Kingdom")) return "Khanate"; // Mongolian
|
||||
if (base === 16 && form === "Principality") return "Beylik"; // Turkic
|
||||
if (base === 5 && (form === "Empire" || form === "Kingdom")) return "Tsardom"; // Ruthenian
|
||||
if ([16, 31].includes(base) && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic, Mongolian
|
||||
if (base === 16 && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic
|
||||
if (base === 12 && (form === "Kingdom" || form === "Grand Duchy")) return "Shogunate"; // Japanese
|
||||
if ([18, 17].includes(base) && form === "Empire") return "Caliphate"; // Arabic, Berber
|
||||
if (base === 18 && (form === "Grand Duchy" || form === "Duchy")) return "Emirate"; // Arabic
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1953,9 +1953,9 @@ window.COArenderer = (function () {
|
|||
return fetchedCharges.join("");
|
||||
}
|
||||
|
||||
const url = location.hostname ? "./charges/" : "http://armoria.herokuapp.com/charges/"; // on local machine fetch files from server
|
||||
const PATH = "./charges/";
|
||||
async function fetchCharge(charge, id) {
|
||||
const fetched = fetch(url + charge + ".svg")
|
||||
const fetched = fetch(PATH + charge + ".svg")
|
||||
.then(res => {
|
||||
if (res.ok) return res.text();
|
||||
else throw new Error("Cannot fetch charge");
|
||||
|
|
|
|||
|
|
@ -71,28 +71,31 @@ window.Cultures = (function () {
|
|||
return;
|
||||
}
|
||||
|
||||
const cell = (c.center = placeCenter(c.sort ? c.sort : i => cells.s[i]));
|
||||
centers.add(cells.p[cell]);
|
||||
const sortingFn = c.sort ? c.sort : i => cells.s[i];
|
||||
const center = placeCenter(sortingFn);
|
||||
|
||||
centers.add(cells.p[center]);
|
||||
c.center = center;
|
||||
c.i = newId;
|
||||
delete c.odd;
|
||||
delete c.sort;
|
||||
c.color = colors[i];
|
||||
c.type = defineCultureType(cell);
|
||||
c.type = defineCultureType(center);
|
||||
c.expansionism = defineCultureExpansionism(c.type);
|
||||
c.origins = [0];
|
||||
c.code = abbreviate(c.name, codes);
|
||||
codes.push(c.code);
|
||||
cultureIds[cell] = newId;
|
||||
cultureIds[center] = newId;
|
||||
if (emblemShape === "random") c.shield = getRandomShield();
|
||||
});
|
||||
|
||||
cells.culture = cultureIds;
|
||||
|
||||
function placeCenter(v) {
|
||||
function placeCenter(sortingFn) {
|
||||
let spacing = (graphWidth + graphHeight) / 2 / count;
|
||||
const MAX_ATTEMPTS = 100;
|
||||
|
||||
const sorted = [...populated].sort((a, b) => v(b) - v(a));
|
||||
const sorted = [...populated].sort((a, b) => sortingFn(b) - sortingFn(a));
|
||||
const max = Math.floor(sorted.length / 2);
|
||||
|
||||
let cellId = 0;
|
||||
|
|
@ -187,12 +190,13 @@ window.Cultures = (function () {
|
|||
name = Names.getCulture(culture, 5, 8, "");
|
||||
base = pack.cultures[culture].base;
|
||||
}
|
||||
|
||||
const code = abbreviate(
|
||||
name,
|
||||
pack.cultures.map(c => c.code)
|
||||
);
|
||||
const i = pack.cultures.length;
|
||||
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex();
|
||||
const color = getRandomColor();
|
||||
|
||||
// define emblem shape
|
||||
let shield = culture.shield;
|
||||
|
|
@ -211,7 +215,7 @@ window.Cultures = (function () {
|
|||
area: 0,
|
||||
rural: 0,
|
||||
urban: 0,
|
||||
origins: [0],
|
||||
origins: [pack.cells.culture[center]],
|
||||
code,
|
||||
shield
|
||||
});
|
||||
|
|
|
|||
|
|
@ -638,19 +638,6 @@ export function resolveVersionConflicts(version) {
|
|||
}
|
||||
|
||||
if (version < 1.91) {
|
||||
// from v1.90.02 texture image is always there
|
||||
if (!texture.select("#textureImage").size()) {
|
||||
// cleanup old texture if it has no id and add new one
|
||||
texture.selectAll("*").remove();
|
||||
texture
|
||||
.append("image")
|
||||
.attr("id", "textureImage")
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("preserveAspectRatio", "xMidYMid slice")
|
||||
.attr("src", "https://i2.wp.com/azgaar.files.wordpress.com/2021/10/marble-big.jpg");
|
||||
}
|
||||
|
||||
// from 1.91.00 custom coa is moved to coa object
|
||||
pack.states.forEach(state => {
|
||||
if (state.coa === "custom") state.coa = {custom: true};
|
||||
|
|
@ -704,19 +691,156 @@ export function resolveVersionConflicts(version) {
|
|||
labels.selectAll("tspan").each(function () {
|
||||
this.setAttribute("x", 0);
|
||||
});
|
||||
}
|
||||
|
||||
// leftover from v1.90.02
|
||||
if (version < 1.94) {
|
||||
// from v1.94.00 texture image is removed when layer is off
|
||||
texture.style("display", null);
|
||||
const textureImage = texture.select("#textureImage");
|
||||
if (textureImage.size()) {
|
||||
const xlink = textureImage.attr("xlink:href");
|
||||
const href = textureImage.attr("href");
|
||||
const src = xlink || href;
|
||||
|
||||
if (src) {
|
||||
textureImage.attr("src", src);
|
||||
textureImage.attr("xlink:href", null);
|
||||
}
|
||||
const textureImage = texture.select("image");
|
||||
if (textureImage.size()) {
|
||||
// restore parameters
|
||||
const x = Number(textureImage.attr("x") || 0);
|
||||
const y = Number(textureImage.attr("y") || 0);
|
||||
const href = textureImage.attr("xlink:href") || textureImage.attr("href") || textureImage.attr("src");
|
||||
// save parameters to parent element
|
||||
texture.attr("data-href", href).attr("data-x", x).attr("data-y", y);
|
||||
// recreate image in expected format
|
||||
textureImage.remove();
|
||||
drawTexture();
|
||||
}
|
||||
}
|
||||
|
||||
if (version < 1.95) {
|
||||
// v1.95.00 added vignette visual layer
|
||||
const mask = defs.append("mask").attr("id", "vignette-mask");
|
||||
mask.append("rect").attr("fill", "white").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
|
||||
mask
|
||||
.append("rect")
|
||||
.attr("id", "vignette-rect")
|
||||
.attr("fill", "black")
|
||||
.attr("x", "0.3%")
|
||||
.attr("y", "0.4%")
|
||||
.attr("width", "99.4%")
|
||||
.attr("height", "99.2%")
|
||||
.attr("rx", "5%")
|
||||
.attr("ry", "5%")
|
||||
.attr("filter", "blur(20px)");
|
||||
|
||||
const vignette = svg
|
||||
.append("g")
|
||||
.attr("id", "vignette")
|
||||
.attr("mask", "url(#vignette-mask)")
|
||||
.attr("opacity", 0.3)
|
||||
.attr("fill", "#000000")
|
||||
.style("display", "none");
|
||||
vignette.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
|
||||
}
|
||||
|
||||
if (version < 1.96) {
|
||||
// v1.96 added ocean rendering for heightmap
|
||||
terrs.selectAll("*").remove();
|
||||
|
||||
const opacity = terrs.attr("opacity");
|
||||
const filter = terrs.attr("filter");
|
||||
const scheme = terrs.attr("scheme") || "bright";
|
||||
const terracing = terrs.attr("terracing");
|
||||
const skip = terrs.attr("skip");
|
||||
const relax = terrs.attr("relax");
|
||||
|
||||
const curveTypes = {0: "curveBasisClosed", 1: "curveLinear", 2: "curveStep"};
|
||||
const curve = curveTypes[terrs.attr("curve")] || "curveBasisClosed";
|
||||
|
||||
terrs
|
||||
.attr("mask", null)
|
||||
.attr("scheme", null)
|
||||
.attr("terracing", null)
|
||||
.attr("skip", null)
|
||||
.attr("relax", null)
|
||||
.attr("curve", null);
|
||||
|
||||
terrs
|
||||
.append("g")
|
||||
.attr("id", "oceanHeights")
|
||||
.attr("data-render", 0)
|
||||
.attr("opacity", opacity)
|
||||
.attr("filter", filter)
|
||||
.attr("scheme", scheme)
|
||||
.attr("terracing", 0)
|
||||
.attr("skip", 0)
|
||||
.attr("relax", 1)
|
||||
.attr("curve", curve);
|
||||
terrs
|
||||
.append("g")
|
||||
.attr("id", "landHeights")
|
||||
.attr("opacity", opacity)
|
||||
.attr("scheme", scheme)
|
||||
.attr("filter", filter)
|
||||
.attr("terracing", terracing)
|
||||
.attr("skip", skip)
|
||||
.attr("relax", relax)
|
||||
.attr("curve", curve)
|
||||
.attr("mask", "url(#land)");
|
||||
|
||||
if (layerIsOn("toggleHeight")) drawHeightmap();
|
||||
|
||||
// v1.96.00 moved scaleBar options from units editor to style
|
||||
d3.select("#scaleBar").remove();
|
||||
|
||||
scaleBar = svg
|
||||
.insert("g", "#viewbox + *")
|
||||
.attr("id", "scaleBar")
|
||||
.attr("opacity", 1)
|
||||
.attr("fill", "#353540")
|
||||
.attr("data-bar-size", 2)
|
||||
.attr("font-size", 10)
|
||||
.attr("data-x", 99)
|
||||
.attr("data-y", 99)
|
||||
.attr("data-label", "");
|
||||
|
||||
scaleBar
|
||||
.append("rect")
|
||||
.attr("id", "scaleBarBack")
|
||||
.attr("opacity", 0.2)
|
||||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#000000")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("filter", "url(#blur5)")
|
||||
.attr("data-top", 20)
|
||||
.attr("data-right", 15)
|
||||
.attr("data-bottom", 15)
|
||||
.attr("data-left", 10);
|
||||
|
||||
drawScaleBar(scaleBar, scale);
|
||||
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||
|
||||
if (!layerIsOn("toggleScaleBar")) scaleBar.style("display", "none");
|
||||
|
||||
// v1.96.00 changed coloring approach for regiments
|
||||
armies.selectAll(":scope > g").each(function () {
|
||||
const fill = this.getAttribute("fill");
|
||||
if (!fill) return;
|
||||
const darkerColor = d3.color(fill).darker().hex();
|
||||
this.setAttribute("color", darkerColor);
|
||||
this.querySelectorAll("g > rect:nth-child(2)").forEach(rect => {
|
||||
rect.setAttribute("fill", "currentColor");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (version < 1.97) {
|
||||
// v1.97.00 changed MFCG link to an arbitrary preview URL
|
||||
options.villageMaxPopulation = 2000;
|
||||
options.showBurgPreview = options.showMFCGMap;
|
||||
delete options.showMFCGMap;
|
||||
|
||||
pack.burgs.forEach(burg => {
|
||||
if (!burg.i || burg.removed) return;
|
||||
|
||||
if (burg.MFCG) {
|
||||
burg.link = getBurgLink(burg);
|
||||
delete burg.MFCG;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,10 +341,7 @@ function cultureChangeColor() {
|
|||
const callback = newFill => {
|
||||
$el.fill = newFill;
|
||||
pack.cultures[cultureId].color = newFill;
|
||||
cults
|
||||
.select("#culture" + cultureId)
|
||||
.attr("fill", newFill)
|
||||
.attr("stroke", newFill);
|
||||
cults.select("#culture" + cultureId).attr("fill", newFill);
|
||||
debug.select("#cultureCenter" + cultureId).attr("fill", newFill);
|
||||
};
|
||||
|
||||
|
|
@ -825,6 +822,7 @@ function addCulture() {
|
|||
|
||||
if (pack.cells.h[center] < 20)
|
||||
return tip("You cannot place culture center into the water. Please click on a land cell", false, "error");
|
||||
|
||||
const occupied = pack.cultures.some(c => !c.removed && c.center === center);
|
||||
if (occupied) return tip("This cell is already a culture center. Please select a different cell", false, "error");
|
||||
|
||||
|
|
@ -860,8 +858,19 @@ function closeCulturesEditor() {
|
|||
}
|
||||
|
||||
async function uploadCulturesData() {
|
||||
const csv = await Formats.csvParser(this.files[0]);
|
||||
const file = this.files[0];
|
||||
this.value = "";
|
||||
const csv = await file.text();
|
||||
const data = d3.csvParse(csv, d => ({
|
||||
i: +d.Id,
|
||||
name: d.Name,
|
||||
color: d.Color,
|
||||
expansionism: +d.Expansionism,
|
||||
type: d.Type,
|
||||
population: +d.Population,
|
||||
emblemsShape: d["Emblems Shape"],
|
||||
origins: d.Origins
|
||||
}));
|
||||
|
||||
const {cultures, cells} = pack;
|
||||
const shapes = Object.keys(COA.shields.types)
|
||||
|
|
@ -873,20 +882,26 @@ async function uploadCulturesData() {
|
|||
if (item.i) item.removed = true;
|
||||
});
|
||||
|
||||
for (const c of csv.iterator((a, b) => +a[0] > +b[0])) {
|
||||
for (const culture of data) {
|
||||
let current;
|
||||
if (+c.id < cultures.length) {
|
||||
current = cultures[c.id];
|
||||
if (culture.i < cultures.length) {
|
||||
current = cultures[culture.i];
|
||||
|
||||
const ratio = current.urban / (current.rural + current.urban);
|
||||
applyPopulationChange(current.rural, current.urban, c.population * (1 - ratio), c.population * ratio, +c.id);
|
||||
applyPopulationChange(
|
||||
current.rural,
|
||||
current.urban,
|
||||
culture.population * (1 - ratio),
|
||||
culture.population * ratio,
|
||||
culture.i
|
||||
);
|
||||
} else {
|
||||
current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origin: 0, rural: 0, urban: 0};
|
||||
cultures.push(current);
|
||||
}
|
||||
|
||||
current.removed = false;
|
||||
current.name = c.name;
|
||||
current.name = culture.name;
|
||||
|
||||
if (current.i) {
|
||||
current.code = abbreviate(
|
||||
|
|
@ -894,10 +909,10 @@ async function uploadCulturesData() {
|
|||
cultures.map(c => c.code)
|
||||
);
|
||||
|
||||
current.color = c.color;
|
||||
current.expansionism = +c.expansionism;
|
||||
current.color = culture.color;
|
||||
current.expansionism = +culture.expansionism;
|
||||
|
||||
if (cultureTypes.includes(c.type)) current.type = c.type;
|
||||
if (cultureTypes.includes(culture.type)) current.type = culture.type;
|
||||
else current.type = "Generic";
|
||||
}
|
||||
|
||||
|
|
@ -916,13 +931,11 @@ async function uploadCulturesData() {
|
|||
current.origins = originIds.filter(id => id !== null);
|
||||
if (!current.origins.length) current.origins = [0];
|
||||
}
|
||||
c.origins = current.i ? restoreOrigins(c.origins) : [null];
|
||||
|
||||
const shieldShape = c["emblems shape"].toLowerCase();
|
||||
if (shapes.includes(shieldShape)) current.shield = shieldShape;
|
||||
else current.shield = "heater";
|
||||
culture.origins = current.i ? restoreOrigins(culture.origins || "") : [null];
|
||||
current.shield = shapes.includes(culture.emblemsShape) ? culture.emblemsShape : "heater";
|
||||
|
||||
const nameBaseIndex = nameBases.findIndex(n => n.name == c.namesbase);
|
||||
const nameBaseIndex = nameBases.findIndex(n => n.name == culture.namesbase);
|
||||
current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ function addListeners() {
|
|||
else if (classList.contains("name")) editStateName(stateId);
|
||||
else if (classList.contains("coaIcon")) editEmblem("state", "stateCOA" + stateId, pack.states[stateId]);
|
||||
else if (classList.contains("icon-star-empty")) stateCapitalZoomIn(stateId);
|
||||
else if (classList.contains("icon-dot-circled")) overviewBurgs({stateId});
|
||||
else if (classList.contains("statePopulation")) changePopulation(stateId);
|
||||
else if (classList.contains("icon-pin")) toggleFog(stateId, classList);
|
||||
else if (classList.contains("icon-trash-empty")) stateRemovePrompt(stateId);
|
||||
|
|
@ -232,7 +233,7 @@ function statesEditorAddLines() {
|
|||
<span class="icon-star-empty placeholder hide"></span>
|
||||
<input class="stateCapital placeholder hide" />
|
||||
<select class="stateCulture placeholder hide">${getCultureOptions(0)}</select>
|
||||
<span data-tip="Burgs count" class="icon-dot-circled hide" style="padding-right: 1px"></span>
|
||||
<span data-tip="Click to overview neutral burgs" class="icon-dot-circled pointer hide" style="padding-right: 1px"></span>
|
||||
<div data-tip="Burgs count" class="stateBurgs hide">${s.burgs}</div>
|
||||
<span data-tip="Neutral lands area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Neutral lands area" class="stateArea hide" style="width: 6em">${si(area)} ${unit}</div>
|
||||
|
|
@ -277,7 +278,7 @@ function statesEditorAddLines() {
|
|||
<select data-tip="Dominant culture. Click to change" class="stateCulture hide">${getCultureOptions(
|
||||
s.culture
|
||||
)}</select>
|
||||
<span data-tip="Burgs count" style="padding-right: 1px" class="icon-dot-circled hide"></span>
|
||||
<span data-tip="Click to overview state burgs" style="padding-right: 1px" class="icon-dot-circled pointer hide"></span>
|
||||
<div data-tip="Burgs count" class="stateBurgs hide">${s.burgs}</div>
|
||||
<span data-tip="State area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="State area" class="stateArea hide" style="width: 6em">${si(area)} ${unit}</div>
|
||||
|
|
@ -433,13 +434,13 @@ function editStateName(state) {
|
|||
modules.editStateName = true;
|
||||
|
||||
// add listeners
|
||||
byId("stateNameEditorShortCulture").on("click", regenerateShortNameCuture);
|
||||
byId("stateNameEditorShortCulture").on("click", regenerateShortNameCulture);
|
||||
byId("stateNameEditorShortRandom").on("click", regenerateShortNameRandom);
|
||||
byId("stateNameEditorAddForm").on("click", addCustomForm);
|
||||
byId("stateNameEditorCustomForm").on("change", addCustomForm);
|
||||
byId("stateNameEditorFullRegenerate").on("click", regenerateFullName);
|
||||
|
||||
function regenerateShortNameCuture() {
|
||||
function regenerateShortNameCulture() {
|
||||
const state = +stateNameEditor.dataset.state;
|
||||
const culture = pack.states[state].culture;
|
||||
const name = Names.getState(Names.getCultureShort(culture), culture);
|
||||
|
|
@ -623,28 +624,36 @@ function stateRemovePrompt(state) {
|
|||
});
|
||||
}
|
||||
|
||||
function stateRemove(state) {
|
||||
statesBody.select("#state" + state).remove();
|
||||
statesBody.select("#state-gap" + state).remove();
|
||||
statesHalo.select("#state-border" + state).remove();
|
||||
labels.select("#stateLabel" + state).remove();
|
||||
defs.select("#textPath_stateLabel" + state).remove();
|
||||
function stateRemove(stateId) {
|
||||
statesBody.select("#state" + stateId).remove();
|
||||
statesBody.select("#state-gap" + stateId).remove();
|
||||
statesHalo.select("#state-border" + stateId).remove();
|
||||
labels.select("#stateLabel" + stateId).remove();
|
||||
defs.select("#textPath_stateLabel" + stateId).remove();
|
||||
|
||||
unfog("focusState" + state);
|
||||
pack.burgs.forEach(b => {
|
||||
if (b.state === state) b.state = 0;
|
||||
unfog("focusState" + stateId);
|
||||
|
||||
pack.burgs.forEach(burg => {
|
||||
if (burg.state === stateId) {
|
||||
burg.state = 0;
|
||||
if (burg.capital) {
|
||||
burg.capital = 0;
|
||||
moveBurgToGroup(burg.i, "towns");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pack.cells.state.forEach((s, i) => {
|
||||
if (s === state) pack.cells.state[i] = 0;
|
||||
if (s === stateId) pack.cells.state[i] = 0;
|
||||
});
|
||||
|
||||
// remove emblem
|
||||
const coaId = "stateCOA" + state;
|
||||
const coaId = "stateCOA" + stateId;
|
||||
byId(coaId).remove();
|
||||
emblems.select(`#stateEmblems > use[data-i='${state}']`).remove();
|
||||
emblems.select(`#stateEmblems > use[data-i='${stateId}']`).remove();
|
||||
|
||||
// remove provinces
|
||||
pack.states[state].provinces.forEach(p => {
|
||||
pack.states[stateId].provinces.forEach(p => {
|
||||
pack.provinces[p] = {i: p, removed: true};
|
||||
pack.cells.province.forEach((pr, i) => {
|
||||
if (pr === p) pack.cells.province[i] = 0;
|
||||
|
|
@ -659,19 +668,14 @@ function stateRemove(state) {
|
|||
});
|
||||
|
||||
// remove military
|
||||
pack.states[state].military.forEach(m => {
|
||||
const id = `regiment${state}-${m.i}`;
|
||||
pack.states[stateId].military.forEach(m => {
|
||||
const id = `regiment${stateId}-${m.i}`;
|
||||
const index = notes.findIndex(n => n.id === id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
});
|
||||
armies.select("g#army" + state).remove();
|
||||
armies.select("g#army" + stateId).remove();
|
||||
|
||||
const capital = pack.states[state].capital;
|
||||
pack.burgs[capital].capital = 0;
|
||||
pack.burgs[capital].state = 0;
|
||||
moveBurgToGroup(capital, "towns");
|
||||
|
||||
pack.states[state] = {i: state, removed: true};
|
||||
pack.states[stateId] = {i: stateId, removed: true};
|
||||
|
||||
debug.selectAll(".highlight").remove();
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
|
|
@ -1393,6 +1397,7 @@ function openStateMergeDialog() {
|
|||
|
||||
function mergeStates(statesToMerge, rulingStateId) {
|
||||
const rulingState = pack.states[rulingStateId];
|
||||
const rulingStateArmy = byId("army" + rulingStateId);
|
||||
|
||||
// remove states to be merged
|
||||
statesToMerge.forEach(stateId => {
|
||||
|
|
@ -1409,27 +1414,25 @@ function openStateMergeDialog() {
|
|||
emblems.select(`#stateEmblems > use[data-i='${stateId}']`).remove();
|
||||
|
||||
// add merged state regiments to the ruling state
|
||||
state.military.forEach(m => {
|
||||
const oldId = `regiment${stateId}-${m.i}`;
|
||||
|
||||
const newRegiment = {...m, i: rulingState.military.length};
|
||||
rulingState.military.push(newRegiment);
|
||||
|
||||
const newId = `regiment${rulingStateId}-${newRegiment.i}`;
|
||||
state.military.forEach(regiment => {
|
||||
const oldId = `regiment${stateId}-${regiment.i}`;
|
||||
const newIndex = rulingState.military.length;
|
||||
rulingState.military.push({...regiment, i: newIndex});
|
||||
const newId = `regiment${rulingStateId}-${newIndex}`;
|
||||
|
||||
const note = notes.find(n => n.id === oldId);
|
||||
if (note) note.id = newId;
|
||||
|
||||
const rulingStateArmy = armies.select("g#army" + rulingStateId);
|
||||
armies
|
||||
.select("g#army" + stateId)
|
||||
.selectAll("g")
|
||||
.each(function () {
|
||||
this.setAttribute("id", newId);
|
||||
rulingStateArmy.node().appendChild(this);
|
||||
});
|
||||
armies.select("g#army" + stateId).remove();
|
||||
const element = byId(oldId);
|
||||
if (element) {
|
||||
element.id = newId;
|
||||
element.dataset.state = rulingStateId;
|
||||
element.dataset.i = newIndex;
|
||||
rulingStateArmy.appendChild(element);
|
||||
}
|
||||
});
|
||||
|
||||
armies.select("g#army" + stateId).remove();
|
||||
});
|
||||
|
||||
// reassing burgs
|
||||
|
|
|
|||
|
|
@ -88,12 +88,6 @@ function getSettings() {
|
|||
heightUnit: heightUnit.value,
|
||||
heightExponent: heightExponentInput.value,
|
||||
temperatureScale: temperatureScale.value,
|
||||
barSize: barSizeInput.value,
|
||||
barLabel: barLabel.value,
|
||||
barBackOpacity: barBackOpacity.value,
|
||||
barBackColor: barBackColor.value,
|
||||
barPosX: barPosX.value,
|
||||
barPosY: barPosY.value,
|
||||
populationRate: populationRate,
|
||||
urbanization: urbanization,
|
||||
mapSize: mapSizeOutput.value,
|
||||
|
|
@ -162,7 +156,7 @@ function getPackCellsData() {
|
|||
religion: dataArrays.religion[cellId],
|
||||
province: dataArrays.province[cellId]
|
||||
})),
|
||||
vertices: pack.vertices.c.map(vertexId => ({
|
||||
vertices: Array.from(pack.vertices.p).map((_, vertexId) => ({
|
||||
i: vertexId,
|
||||
p: pack.vertices.p[vertexId],
|
||||
v: pack.vertices.v[vertexId],
|
||||
|
|
@ -203,11 +197,11 @@ function getGridCellsData() {
|
|||
temp: dataArrays.temp[cellId],
|
||||
prec: dataArrays.prec[cellId]
|
||||
})),
|
||||
vertices: grid.vertices.c.map(vertexId => ({
|
||||
vertices: Array.from(grid.vertices.p).map((_, vertexId) => ({
|
||||
i: vertexId,
|
||||
p: pack.vertices.p[vertexId],
|
||||
v: pack.vertices.v[vertexId],
|
||||
c: pack.vertices.c[vertexId]
|
||||
p: grid.vertices.p[vertexId],
|
||||
v: grid.vertices.v[vertexId],
|
||||
c: grid.vertices.c[vertexId]
|
||||
})),
|
||||
cellsDesired: grid.cellsDesired,
|
||||
spacing: grid.spacing,
|
||||
|
|
|
|||
|
|
@ -150,6 +150,10 @@ function appendStyleSheet() {
|
|||
}
|
||||
|
||||
function insertHtml() {
|
||||
const heightmapColorSchemeOptions = Object.keys(heightmapColorSchemes)
|
||||
.map(scheme => `<option value="${scheme}">${scheme}</option>`)
|
||||
.join("");
|
||||
|
||||
const heightmapSelectionHtml = /* html */ `<div id="heightmapSelection" class="dialog stable">
|
||||
<div class="heightmap-selection">
|
||||
<section data-tip="Select heightmap template – template provides unique, but similar-looking maps on generation">
|
||||
|
|
@ -174,12 +178,7 @@ function insertHtml() {
|
|||
</div>
|
||||
<div data-tip="Color scheme used for heightmap preview">
|
||||
Color scheme
|
||||
<select id="heightmapSelectionColorScheme">
|
||||
<option value="bright" selected>Bright</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="monochrome">Monochrome</option>
|
||||
</select>
|
||||
<select id="heightmapSelectionColorScheme">${heightmapColorSchemeOptions}</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -200,10 +199,9 @@ function insertHtml() {
|
|||
const name = heightmapTemplates[key].name;
|
||||
Math.random = aleaPRNG(initialSeed);
|
||||
const heights = HeightmapGenerator.fromTemplate(graph, key);
|
||||
const dataUrl = drawHeights(heights);
|
||||
|
||||
return /* html */ `<article data-id="${key}" data-seed="${initialSeed}">
|
||||
<img src="${dataUrl}" alt="${name}" />
|
||||
<img src="${getHeightmapPreview(heights)}" alt="${name}" />
|
||||
<div>
|
||||
${name}
|
||||
<span data-tip="Regenerate preview" class="icon-cw regeneratePreview"></span>
|
||||
|
|
@ -267,43 +265,16 @@ function getGraph(currentGraph) {
|
|||
return newGraph;
|
||||
}
|
||||
|
||||
function drawHeights(heights) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = graph.cellsX;
|
||||
canvas.height = graph.cellsY;
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imageData = ctx.createImageData(graph.cellsX, graph.cellsY);
|
||||
|
||||
const schemeId = byId("heightmapSelectionColorScheme").value;
|
||||
const scheme = getColorScheme(schemeId);
|
||||
const renderOcean = byId("heightmapSelectionRenderOcean").checked;
|
||||
const getHeight = height => (height < 20 ? (renderOcean ? height : 0) : height);
|
||||
|
||||
for (let i = 0; i < heights.length; i++) {
|
||||
const color = scheme(1 - getHeight(heights[i]) / 100);
|
||||
const {r, g, b} = d3.color(color);
|
||||
|
||||
const n = i * 4;
|
||||
imageData.data[n] = r;
|
||||
imageData.data[n + 1] = g;
|
||||
imageData.data[n + 2] = b;
|
||||
imageData.data[n + 3] = 255;
|
||||
}
|
||||
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
return canvas.toDataURL("image/png");
|
||||
}
|
||||
|
||||
function drawTemplatePreview(id) {
|
||||
const heights = HeightmapGenerator.fromTemplate(graph, id);
|
||||
const dataUrl = drawHeights(heights);
|
||||
const dataUrl = getHeightmapPreview(heights);
|
||||
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
|
||||
article.querySelector("img").src = dataUrl;
|
||||
}
|
||||
|
||||
async function drawPrecreatedHeightmap(id) {
|
||||
const heights = await HeightmapGenerator.fromPrecreated(graph, id);
|
||||
const dataUrl = drawHeights(heights);
|
||||
const dataUrl = getHeightmapPreview(heights);
|
||||
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
|
||||
article.querySelector("img").src = dataUrl;
|
||||
}
|
||||
|
|
@ -339,3 +310,10 @@ function confirmHeightmapEdit() {
|
|||
onConfirm: () => editHeightmap({mode: "erase", tool})
|
||||
});
|
||||
}
|
||||
|
||||
function getHeightmapPreview(heights) {
|
||||
const scheme = getColorScheme(byId("heightmapSelectionColorScheme").value);
|
||||
const renderOcean = byId("heightmapSelectionRenderOcean").checked;
|
||||
const dataUrl = drawHeights({heights, width: graph.cellsX, height: graph.cellsY, scheme, renderOcean});
|
||||
return dataUrl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -535,4 +535,15 @@ Rob Frantz
|
|||
Driver
|
||||
Tr4v3l3r
|
||||
Cooper Cantrell
|
||||
Maximilien Bouillot`;
|
||||
Maximilien Bouillot
|
||||
J.E. Ellis
|
||||
Igor
|
||||
John Todd
|
||||
burning.rosary
|
||||
Shane Roppel
|
||||
Hank Agrippa
|
||||
Noah Morris
|
||||
Phil Karecki
|
||||
Matthew Jarocki
|
||||
Lucius Licinius Lucullus
|
||||
Andrew Haney`;
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ window.Cloud = (function () {
|
|||
|
||||
async save(fileName, contents) {
|
||||
const resp = await this.call("filesUpload", {path: "/" + fileName, contents});
|
||||
DEBUG && console.log("Dropbox response:", resp);
|
||||
DEBUG && console.info("Dropbox response:", resp);
|
||||
return true;
|
||||
},
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ window.Cloud = (function () {
|
|||
|
||||
// Callback function for auth window
|
||||
async setDropBoxToken(token) {
|
||||
DEBUG && console.log("Access token:", token);
|
||||
DEBUG && console.info("Access token:", token);
|
||||
setToken(this.name, token);
|
||||
await this.connect(token);
|
||||
this.authWindow.close();
|
||||
|
|
@ -118,9 +118,9 @@ window.Cloud = (function () {
|
|||
},
|
||||
|
||||
async getLink(path) {
|
||||
// return existitng shared link
|
||||
// return existing shared link
|
||||
const sharedLinks = await this.call("sharingListSharedLinks", {path});
|
||||
if (sharedLinks.result.links.length) return resp.result.links[0].url;
|
||||
if (sharedLinks.result.links.length) return sharedLinks.result.links[0].url;
|
||||
|
||||
// create new shared link
|
||||
const settings = {
|
||||
|
|
@ -131,7 +131,7 @@ window.Cloud = (function () {
|
|||
allow_download: true
|
||||
};
|
||||
const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings});
|
||||
DEBUG && console.log("Dropbox link object:", resp.result);
|
||||
DEBUG && console.info("Dropbox link object:", resp.result);
|
||||
return resp.result.url;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,27 +1,21 @@
|
|||
"use strict";
|
||||
// Functions to export map to image or data files
|
||||
|
||||
// download map as SVG
|
||||
async function saveSVG() {
|
||||
TIME && console.time("saveSVG");
|
||||
async function exportToSvg() {
|
||||
TIME && console.time("exportToSvg");
|
||||
const url = await getMapURL("svg", {fullMap: true});
|
||||
const link = document.createElement("a");
|
||||
link.download = getFileName() + ".svg";
|
||||
link.href = url;
|
||||
link.click();
|
||||
|
||||
tip(
|
||||
`${link.download} is saved. Open "Downloads" screen (ctrl + J) to check. You can set image scale in options`,
|
||||
true,
|
||||
"success",
|
||||
5000
|
||||
);
|
||||
TIME && console.timeEnd("saveSVG");
|
||||
const message = `${link.download} is saved. Open 'Downloads' screen (ctrl + J) to check`;
|
||||
tip(message, true, "success", 5000);
|
||||
TIME && console.timeEnd("exportToSvg");
|
||||
}
|
||||
|
||||
// download map as PNG
|
||||
async function savePNG() {
|
||||
TIME && console.time("savePNG");
|
||||
async function exportToPng() {
|
||||
TIME && console.time("exportToPng");
|
||||
const url = await getMapURL("png");
|
||||
|
||||
const link = document.createElement("a");
|
||||
|
|
@ -41,22 +35,18 @@ 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
|
||||
);
|
||||
|
||||
const message = `${link.download} is saved. Open 'Downloads' screen (ctrl + J) to check. You can set image scale in options`;
|
||||
tip(message, true, "success", 5000);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
TIME && console.timeEnd("savePNG");
|
||||
TIME && console.timeEnd("exportToPng");
|
||||
}
|
||||
|
||||
// download map as JPEG
|
||||
async function saveJPEG() {
|
||||
TIME && console.time("saveJPEG");
|
||||
async function exportToJpeg() {
|
||||
TIME && console.time("exportToJpeg");
|
||||
const url = await getMapURL("png");
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
|
|
@ -77,103 +67,122 @@ async function saveJPEG() {
|
|||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
};
|
||||
|
||||
TIME && console.timeEnd("saveJPEG");
|
||||
TIME && console.timeEnd("exportToJpeg");
|
||||
}
|
||||
|
||||
// download map as png tiles
|
||||
async function saveTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
|
||||
await import("../../libs/jszip.min.js");
|
||||
const zip = new window.JSZip();
|
||||
async function exportToPngTiles() {
|
||||
const status = byId("tileStatus");
|
||||
status.innerHTML = "Preparing files...";
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
|
||||
await import("../../libs/jszip.min.js");
|
||||
const zip = new window.JSZip();
|
||||
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
imgSchema.onload = function () {
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob));
|
||||
};
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
|
||||
// download tiles
|
||||
const url = await getMapURL("tiles", {fullMap: true});
|
||||
const tilesX = +document.getElementById("tileColsInput").value;
|
||||
const tilesY = +document.getElementById("tileRowsInput").value;
|
||||
const scale = +document.getElementById("tileScaleInput").value;
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
await loadImage(imgSchema);
|
||||
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
status.innerHTML = "Drawing schema...";
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
const blob = await canvasToBlob(canvas, "image/png");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
zip.file("schema.png", blob);
|
||||
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
// download tiles
|
||||
const url = await getMapURL("tiles", {fullMap: true});
|
||||
const tilesX = +byId("tileColsInput").value;
|
||||
const tilesY = +byId("tileRowsInput").value;
|
||||
const scale = +byId("tileScaleInput").value;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
|
||||
let loaded = 0;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const name = `fmg_tile_${i}.png`;
|
||||
canvas.toBlob(blob => {
|
||||
zip.file(name, blob);
|
||||
loaded += 1;
|
||||
if (loaded === tolesTotal) return downloadZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
|
||||
function downloadZip() {
|
||||
const name = `${getFileName()}.zip`;
|
||||
zip.generateAsync({type: "blob"}).then(blob => {
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
link.click();
|
||||
link.remove();
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
resolve(true);
|
||||
});
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
await loadImage(img);
|
||||
|
||||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (let y = 0, row = 0, id = 1; y + tileH <= graphHeight; y += tileH, row++) {
|
||||
const rowName = alphabet[row % alphabet.length];
|
||||
|
||||
for (let x = 0, cell = 1; x + tileW <= graphWidth; x += tileW, cell++, id++) {
|
||||
status.innerHTML = `Drawing tile ${rowName}${cell} (${id} of ${tolesTotal})...`;
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const blob = await canvasToBlob(canvas, "image/png");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
zip.file(`${rowName}${cell}.png`, blob);
|
||||
}
|
||||
}
|
||||
|
||||
status.innerHTML = "Zipping files...";
|
||||
zip.generateAsync({type: "blob"}).then(blob => {
|
||||
status.innerHTML = "Downloading the archive...";
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = getFileName() + ".zip";
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
status.innerHTML = 'Done. Check .zip file in "Downloads" (crtl + J)';
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
});
|
||||
|
||||
// promisified img.onload
|
||||
function loadImage(img) {
|
||||
return new Promise((resolve, reject) => {
|
||||
img.onload = () => resolve();
|
||||
img.onerror = err => reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
// promisified canvas.toBlob
|
||||
function canvasToBlob(canvas, mimeType, qualityArgument = 1) {
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob(
|
||||
blob => {
|
||||
if (blob) resolve(blob);
|
||||
else reject(new Error("Canvas toBlob() error"));
|
||||
},
|
||||
mimeType,
|
||||
qualityArgument
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// parse map svg to object url
|
||||
async function getMapURL(type, options = {}) {
|
||||
async function getMapURL(type, options) {
|
||||
const {
|
||||
debug = false,
|
||||
globe = false,
|
||||
noLabels = false,
|
||||
noWater = false,
|
||||
noScaleBar = false,
|
||||
noIce = false,
|
||||
fullMap = false
|
||||
} = options;
|
||||
} = options || {};
|
||||
|
||||
if (fullMap) drawScaleBar(1);
|
||||
|
||||
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
|
||||
const cloneEl = byId("map").cloneNode(true); // clone svg
|
||||
cloneEl.id = "fantasyMap";
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (!debug) clone.select("#debug")?.remove();
|
||||
|
||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
||||
const svgDefs = document.getElementById("defElements");
|
||||
const svgDefs = byId("defElements");
|
||||
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
||||
if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove();
|
||||
if (globe) clone.select("#scaleBar")?.remove();
|
||||
if (noLabels) {
|
||||
clone.select("#labels #states")?.remove();
|
||||
clone.select("#labels #burgLabels")?.remove();
|
||||
|
|
@ -183,14 +192,18 @@ async function getMapURL(type, options = {}) {
|
|||
clone.select("#oceanBase").attr("opacity", 0);
|
||||
clone.select("#oceanPattern").attr("opacity", 0);
|
||||
}
|
||||
if (noScaleBar) clone.select("#scaleBar")?.remove();
|
||||
if (noIce) clone.select("#ice")?.remove();
|
||||
if (fullMap) {
|
||||
// reset transform to show the whole map
|
||||
clone.attr("width", graphWidth).attr("height", graphHeight);
|
||||
clone.select("#viewbox").attr("transform", null);
|
||||
drawScaleBar(scale);
|
||||
|
||||
if (!noScaleBar) {
|
||||
drawScaleBar(clone.select("#scaleBar"), 1);
|
||||
fitScaleBar(clone.select("#scaleBar"), graphWidth, graphHeight);
|
||||
}
|
||||
}
|
||||
if (noScaleBar) clone.select("#scaleBar")?.remove();
|
||||
|
||||
if (type === "svg") removeUnusedElements(clone);
|
||||
if (customization && type === "mesh") updateMeshCells(clone);
|
||||
|
|
@ -229,35 +242,35 @@ async function getMapURL(type, options = {}) {
|
|||
.forEach(el => {
|
||||
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
|
||||
if (!href) return;
|
||||
const emblem = document.getElementById(href.slice(1));
|
||||
const emblem = byId(href.slice(1));
|
||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
||||
});
|
||||
} else {
|
||||
cloneDefs.querySelector("#defs-emblems")?.remove();
|
||||
}
|
||||
|
||||
// replace ocean pattern href to base64
|
||||
if (location.hostname) {
|
||||
const el = cloneEl.getElementById("oceanicPattern");
|
||||
const url = el?.getAttribute("href");
|
||||
if (url) {
|
||||
{
|
||||
// replace ocean pattern href to base64
|
||||
const image = cloneEl.getElementById("oceanicPattern");
|
||||
const href = image?.getAttribute("href");
|
||||
if (href) {
|
||||
await new Promise(resolve => {
|
||||
getBase64(url, base64 => {
|
||||
el.setAttribute("href", base64);
|
||||
getBase64(href, base64 => {
|
||||
image.setAttribute("href", base64);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// replace texture href to base64
|
||||
if (location.hostname) {
|
||||
const el = cloneEl.getElementById("textureImage");
|
||||
const url = el?.getAttribute("href");
|
||||
if (url) {
|
||||
{
|
||||
// replace texture href to base64
|
||||
const image = cloneEl.querySelector("#texture > image");
|
||||
const href = image?.getAttribute("href");
|
||||
if (href) {
|
||||
await new Promise(resolve => {
|
||||
getBase64(url, base64 => {
|
||||
el.setAttribute("href", base64);
|
||||
getBase64(href, base64 => {
|
||||
image.setAttribute("href", base64);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
|
@ -375,7 +388,7 @@ function removeUnusedElements(clone) {
|
|||
|
||||
function updateMeshCells(clone) {
|
||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
||||
const scheme = getColorScheme(terrs.attr("scheme"));
|
||||
const scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
|
||||
clone.select("#heights").attr("filter", "url(#blur1)");
|
||||
clone
|
||||
.select("#heights")
|
||||
|
|
@ -400,12 +413,6 @@ function inlineStyle(clone) {
|
|||
const key = compStyle[i];
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
// Firefox mask hack
|
||||
if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) {
|
||||
style += "mask-image: url('#land');";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "cursor") continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
window.Formats = (function () {
|
||||
async function csvParser(file, separator = ",") {
|
||||
const txt = await file.text();
|
||||
const rows = txt.split("\n");
|
||||
const headers = rows
|
||||
.shift()
|
||||
.split(separator)
|
||||
.map(x => x.toLowerCase());
|
||||
const data = rows.filter(a => a.trim() !== "").map(r => r.split(separator));
|
||||
|
||||
return {
|
||||
headers,
|
||||
data,
|
||||
iterator: function* (sortf) {
|
||||
const dataset = sortf ? this.data.sort(sortf) : this.data;
|
||||
for (const d of dataset) yield Object.fromEntries(d.map((a, i) => [this.headers[i], a]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {csvParser};
|
||||
})();
|
||||
|
|
@ -10,33 +10,30 @@ async function quickLoad() {
|
|||
}
|
||||
|
||||
async function loadFromDropbox() {
|
||||
const mapPath = document.getElementById("loadFromDropboxSelect")?.value;
|
||||
const mapPath = byId("loadFromDropboxSelect")?.value;
|
||||
|
||||
DEBUG && console.log("Loading map from Dropbox:", mapPath);
|
||||
DEBUG && console.info("Loading map from Dropbox:", mapPath);
|
||||
const blob = await Cloud.providers.dropbox.load(mapPath);
|
||||
uploadMap(blob);
|
||||
}
|
||||
|
||||
async function createSharableDropboxLink() {
|
||||
const mapFile = document.querySelector("#loadFromDropbox select").value;
|
||||
const sharableLink = document.getElementById("sharableLink");
|
||||
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||
let url;
|
||||
const sharableLink = byId("sharableLink");
|
||||
const sharableLinkContainer = byId("sharableLinkContainer");
|
||||
|
||||
try {
|
||||
url = await Cloud.providers.dropbox.getLink(mapFile);
|
||||
} catch {
|
||||
const previewLink = await Cloud.providers.dropbox.getLink(mapFile);
|
||||
const directLink = previewLink.replace("www.dropbox.com", "dl.dropboxusercontent.com"); // DL allows CORS
|
||||
const finalLink = `${location.origin}${location.pathname}?maplink=${directLink}`;
|
||||
|
||||
sharableLink.innerText = finalLink.slice(0, 45) + "...";
|
||||
sharableLink.setAttribute("href", finalLink);
|
||||
sharableLinkContainer.style.display = "block";
|
||||
} catch (error) {
|
||||
ERROR && console.error(error);
|
||||
return tip("Dropbox API error. Can not create link.", true, "error", 2000);
|
||||
}
|
||||
|
||||
const fmg = window.location.href.split("?")[0];
|
||||
const reallink = `${fmg}?maplink=${url}`;
|
||||
// voodoo magic required by the yellow god of CORS
|
||||
const link = reallink.replace("www.dropbox.com/s/", "dl.dropboxusercontent.com/1/view/");
|
||||
const shortLink = link.slice(0, 50) + "...";
|
||||
|
||||
sharableLinkContainer.style.display = "block";
|
||||
sharableLink.innerText = shortLink;
|
||||
sharableLink.setAttribute("href", link);
|
||||
}
|
||||
|
||||
function loadMapPrompt(blob) {
|
||||
|
|
@ -113,7 +110,7 @@ function uploadMap(file, callback) {
|
|||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = async function (fileLoadedEvent) {
|
||||
if (callback) callback();
|
||||
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
|
||||
byId("coas").innerHTML = ""; // remove auto-generated emblems
|
||||
const result = fileLoadedEvent.target.result;
|
||||
const [mapData, mapVersion] = await parseLoadedResult(result);
|
||||
|
||||
|
|
@ -186,22 +183,22 @@ function showUploadMessage(type, mapData, mapVersion) {
|
|||
title = "Newer file";
|
||||
canBeLoaded = false;
|
||||
} else if (type === "outdated") {
|
||||
message = `The map version (${mapVersion}) does not match the Generator version (${version}).<br>That is fine, click OK to the get map <b style="color: #005000">auto-updated</b>.<br>In case of issues please keep using an ${archive} of the Generator`;
|
||||
title = "Outdated file";
|
||||
canBeLoaded = true;
|
||||
INFO && console.info(`Loading map. Auto-update from ${mapVersion} to ${version}`);
|
||||
parseLoadedData(mapData, mapVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
alertMessage.innerHTML = message;
|
||||
const buttons = {
|
||||
OK: function () {
|
||||
$(this).dialog("close");
|
||||
if (canBeLoaded) parseLoadedData(mapData);
|
||||
if (canBeLoaded) parseLoadedData(mapData, mapVersion);
|
||||
}
|
||||
};
|
||||
$("#alert").dialog({title, buttons});
|
||||
}
|
||||
|
||||
async function parseLoadedData(data) {
|
||||
async function parseLoadedData(data, mapVersion) {
|
||||
try {
|
||||
// exit customization
|
||||
if (window.closeDialogs) closeDialogs();
|
||||
|
|
@ -221,6 +218,7 @@ async function parseLoadedData(data) {
|
|||
|
||||
INFO && console.group("Loaded Map " + seed);
|
||||
|
||||
// TODO: move all to options object
|
||||
void (function parseSettings() {
|
||||
const settings = data[1].split("|");
|
||||
if (settings[0]) applyOption(distanceUnitInput, settings[0]);
|
||||
|
|
@ -229,23 +227,16 @@ async function parseLoadedData(data) {
|
|||
if (settings[3]) applyOption(heightUnit, settings[3]);
|
||||
if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4];
|
||||
if (settings[5]) temperatureScale.value = settings[5];
|
||||
if (settings[6]) barSizeInput.value = barSizeOutput.value = settings[6];
|
||||
if (settings[7] !== undefined) barLabel.value = settings[7];
|
||||
if (settings[8] !== undefined) barBackOpacity.value = settings[8];
|
||||
if (settings[9]) barBackColor.value = settings[9];
|
||||
if (settings[10]) barPosX.value = settings[10];
|
||||
if (settings[11]) barPosY.value = settings[11];
|
||||
// setting 6-11 (scaleBar) are part of style now, kept as "" in newer versions for compatibility
|
||||
if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12];
|
||||
if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13];
|
||||
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100);
|
||||
if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100);
|
||||
if (settings[18]) precInput.value = precOutput.value = settings[18];
|
||||
|
||||
if (settings[19]) options = JSON.parse(settings[19]);
|
||||
// setting 16 and 17 (temperature) are part of options now, kept as "" in newer versions for compatibility
|
||||
if (settings[16]) options.temperatureEquator = +settings[16];
|
||||
if (settings[17]) options.temperatureNorthPole = options.temperatureSouthPole = +settings[17];
|
||||
|
||||
if (settings[20]) mapName.value = settings[20];
|
||||
if (settings[21]) hideLabels.checked = +settings[21];
|
||||
if (settings[22]) stylePreset.value = settings[22];
|
||||
|
|
@ -351,6 +342,15 @@ async function parseLoadedData(data) {
|
|||
statePaths?.remove();
|
||||
})();
|
||||
|
||||
void (function addMissingElements() {
|
||||
if (!texture.size()) {
|
||||
texture = viewbox
|
||||
.insert("g", "#landmass")
|
||||
.attr("id", "texture")
|
||||
.attr("data-href", "./images/textures/plaster.jpg");
|
||||
}
|
||||
})();
|
||||
|
||||
void (function parseGridData() {
|
||||
grid = JSON.parse(data[6]);
|
||||
|
||||
|
|
@ -404,46 +404,46 @@ async function parseLoadedData(data) {
|
|||
})();
|
||||
|
||||
void (function restoreLayersState() {
|
||||
// helper functions
|
||||
const notHidden = selection => selection.node() && selection.style("display") !== "none";
|
||||
const isVisible = selection => selection.node() && selection.style("display") !== "none";
|
||||
const isVisibleNode = node => node && node.style.display !== "none";
|
||||
const hasChildren = selection => selection.node()?.hasChildNodes();
|
||||
const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
|
||||
const turnOn = el => document.getElementById(el).classList.remove("buttonoff");
|
||||
const turnOn = el => byId(el).classList.remove("buttonoff");
|
||||
|
||||
// turn all layers off
|
||||
document
|
||||
.getElementById("mapLayers")
|
||||
byId("mapLayers")
|
||||
.querySelectorAll("li")
|
||||
.forEach(el => el.classList.add("buttonoff"));
|
||||
|
||||
// turn on active layers
|
||||
if (notHidden(texture) && hasChild(texture, "image")) turnOn("toggleTexture");
|
||||
if (hasChild(texture, "image")) turnOn("toggleTexture");
|
||||
if (hasChildren(terrs)) turnOn("toggleHeight");
|
||||
if (hasChildren(biomes)) turnOn("toggleBiomes");
|
||||
if (hasChildren(cells)) turnOn("toggleCells");
|
||||
if (hasChildren(gridOverlay)) turnOn("toggleGrid");
|
||||
if (hasChildren(coordinates)) turnOn("toggleCoordinates");
|
||||
if (notHidden(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
|
||||
if (isVisible(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
|
||||
if (hasChildren(rivers)) turnOn("toggleRivers");
|
||||
if (notHidden(terrain) && hasChildren(terrain)) turnOn("toggleRelief");
|
||||
if (isVisible(terrain) && hasChildren(terrain)) turnOn("toggleRelief");
|
||||
if (hasChildren(relig)) turnOn("toggleReligions");
|
||||
if (hasChildren(cults)) turnOn("toggleCultures");
|
||||
if (hasChildren(statesBody)) turnOn("toggleStates");
|
||||
if (hasChildren(provs)) turnOn("toggleProvinces");
|
||||
if (hasChildren(zones) && notHidden(zones)) turnOn("toggleZones");
|
||||
if (notHidden(borders) && hasChild(borders, "path")) turnOn("toggleBorders");
|
||||
if (notHidden(routes) && hasChild(routes, "path")) turnOn("toggleRoutes");
|
||||
if (hasChildren(zones) && isVisible(zones)) turnOn("toggleZones");
|
||||
if (isVisible(borders) && hasChild(borders, "path")) turnOn("toggleBorders");
|
||||
if (isVisible(routes) && hasChild(routes, "path")) turnOn("toggleRoutes");
|
||||
if (hasChildren(temperature)) turnOn("toggleTemp");
|
||||
if (hasChild(population, "line")) turnOn("togglePopulation");
|
||||
if (hasChildren(ice)) turnOn("toggleIce");
|
||||
if (hasChild(prec, "circle")) turnOn("togglePrec");
|
||||
if (notHidden(emblems) && hasChild(emblems, "use")) turnOn("toggleEmblems");
|
||||
if (notHidden(labels)) turnOn("toggleLabels");
|
||||
if (notHidden(icons)) turnOn("toggleIcons");
|
||||
if (hasChildren(armies) && notHidden(armies)) turnOn("toggleMilitary");
|
||||
if (isVisible(emblems) && hasChild(emblems, "use")) turnOn("toggleEmblems");
|
||||
if (isVisible(labels)) turnOn("toggleLabels");
|
||||
if (isVisible(icons)) turnOn("toggleIcons");
|
||||
if (hasChildren(armies) && isVisible(armies)) turnOn("toggleMilitary");
|
||||
if (hasChildren(markers)) turnOn("toggleMarkers");
|
||||
if (notHidden(ruler)) turnOn("toggleRulers");
|
||||
if (notHidden(scaleBar)) turnOn("toggleScaleBar");
|
||||
if (isVisible(ruler)) turnOn("toggleRulers");
|
||||
if (isVisible(scaleBar)) turnOn("toggleScaleBar");
|
||||
if (isVisibleNode(byId("vignette"))) turnOn("toggleVignette");
|
||||
|
||||
getCurrentPreset();
|
||||
})();
|
||||
|
|
@ -456,17 +456,31 @@ async function parseLoadedData(data) {
|
|||
})();
|
||||
|
||||
{
|
||||
// dynamically import and run auto-udpdate script
|
||||
// dynamically import and run auto-update script
|
||||
const versionNumber = parseFloat(params[0]);
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.93.00");
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.97.04");
|
||||
resolveVersionConflicts(versionNumber);
|
||||
}
|
||||
|
||||
// add custom heightmap color scheme if any
|
||||
if (heightmapColorSchemes) {
|
||||
const oceanScheme = byId("oceanHeights")?.getAttribute("scheme");
|
||||
if (oceanScheme && !(oceanScheme in heightmapColorSchemes)) addCustomColorScheme(oceanScheme);
|
||||
const landScheme = byId("#landHeights")?.getAttribute("scheme");
|
||||
if (landScheme && !(landScheme in heightmapColorSchemes)) addCustomColorScheme(landScheme);
|
||||
}
|
||||
|
||||
{
|
||||
// add custom texture if any
|
||||
const textureHref = texture.attr("data-href");
|
||||
if (textureHref) updateTextureSelectValue(textureHref);
|
||||
}
|
||||
|
||||
void (function checkDataIntegrity() {
|
||||
const cells = pack.cells;
|
||||
|
||||
if (pack.cells.i.length !== pack.cells.state.length) {
|
||||
const message = "Data Integrity Check. Striping issue detected. To fix edit the heightmap in erase mode";
|
||||
const message = "Data integrity check. Striping issue detected. To fix edit the heightmap in ERASE mode";
|
||||
ERROR && console.error(message);
|
||||
}
|
||||
|
||||
|
|
@ -474,7 +488,7 @@ async function parseLoadedData(data) {
|
|||
invalidStates.forEach(s => {
|
||||
const invalidCells = cells.i.filter(i => cells.state[i] === s);
|
||||
invalidCells.forEach(i => (cells.state[i] = 0));
|
||||
ERROR && console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells);
|
||||
ERROR && console.error("Data integrity check. Invalid state", s, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidProvinces = [...new Set(cells.province)].filter(
|
||||
|
|
@ -483,14 +497,14 @@ async function parseLoadedData(data) {
|
|||
invalidProvinces.forEach(p => {
|
||||
const invalidCells = cells.i.filter(i => cells.province[i] === p);
|
||||
invalidCells.forEach(i => (cells.province[i] = 0));
|
||||
ERROR && console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells);
|
||||
ERROR && console.error("Data integrity check. Invalid province", p, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed);
|
||||
invalidCultures.forEach(c => {
|
||||
const invalidCells = cells.i.filter(i => cells.culture[i] === c);
|
||||
invalidCells.forEach(i => (cells.province[i] = 0));
|
||||
ERROR && console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells);
|
||||
ERROR && console.error("Data integrity check. Invalid culture", c, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidReligions = [...new Set(cells.religion)].filter(
|
||||
|
|
@ -499,14 +513,14 @@ async function parseLoadedData(data) {
|
|||
invalidReligions.forEach(r => {
|
||||
const invalidCells = cells.i.filter(i => cells.religion[i] === r);
|
||||
invalidCells.forEach(i => (cells.religion[i] = 0));
|
||||
ERROR && console.error("Data Integrity Check. Invalid religion", r, "is assigned to cells", invalidCells);
|
||||
ERROR && console.error("Data integrity check. Invalid religion", r, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidFeatures = [...new Set(cells.f)].filter(f => f && !pack.features[f]);
|
||||
invalidFeatures.forEach(f => {
|
||||
const invalidCells = cells.i.filter(i => cells.f[i] === f);
|
||||
// No fix as for now
|
||||
ERROR && console.error("Data Integrity Check. Invalid feature", f, "is assigned to cells", invalidCells);
|
||||
ERROR && console.error("Data integrity check. Invalid feature", f, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidBurgs = [...new Set(cells.burg)].filter(
|
||||
|
|
@ -515,7 +529,7 @@ async function parseLoadedData(data) {
|
|||
invalidBurgs.forEach(burgId => {
|
||||
const invalidCells = cells.i.filter(i => cells.burg[i] === burgId);
|
||||
invalidCells.forEach(i => (cells.burg[i] = 0));
|
||||
ERROR && console.error("Data Integrity Check. Invalid burg", burgId, "is assigned to cells", invalidCells);
|
||||
ERROR && console.error("Data integrity check. Invalid burg", burgId, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidRivers = [...new Set(cells.r)].filter(r => r && !pack.rivers.find(river => river.i === r));
|
||||
|
|
@ -523,60 +537,112 @@ async function parseLoadedData(data) {
|
|||
const invalidCells = cells.i.filter(i => cells.r[i] === r);
|
||||
invalidCells.forEach(i => (cells.r[i] = 0));
|
||||
rivers.select("river" + r).remove();
|
||||
ERROR && console.error("Data Integrity Check. Invalid river", r, "is assigned to cells", invalidCells);
|
||||
ERROR && console.error("Data integrity check. Invalid river", r, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
pack.burgs.forEach(burg => {
|
||||
if ((!burg.i || burg.removed) && burg.lock) {
|
||||
if (typeof burg.capital === "boolean") burg.capital = Number(burg.capital);
|
||||
|
||||
if (!burg.i && burg.lock) {
|
||||
ERROR && console.error(`Data integrity check. Burg 0 is marked as locked, removing the status`);
|
||||
delete burg.lock;
|
||||
return;
|
||||
}
|
||||
|
||||
if (burg.removed && burg.lock) {
|
||||
ERROR &&
|
||||
console.error(
|
||||
`Data Integrity Check. Burg ${burg.i || "0"} is removed or invalid but still locked. Unlocking the burg`
|
||||
);
|
||||
console.error(`Data integrity check. Removed burg ${burg.i} is marked as locked. Unlocking the burg`);
|
||||
delete burg.lock;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!burg.i || burg.removed) return;
|
||||
|
||||
if (burg.cell === undefined || burg.x === undefined || burg.y === undefined) {
|
||||
ERROR &&
|
||||
console.error(
|
||||
`Data Integrity Check. Burg ${burg.i} is missing cell info or coordinates. Removing the burg`
|
||||
`Data integrity check. Burg ${burg.i} is missing cell info or coordinates. Removing the burg`
|
||||
);
|
||||
burg.removed = true;
|
||||
}
|
||||
|
||||
if (burg.port < 0) {
|
||||
ERROR && console.error("Data Integrity Check. Burg", burg.i, "has invalid port value", burg.port);
|
||||
ERROR && console.error("Data integrity check. Burg", burg.i, "has invalid port value", burg.port);
|
||||
burg.port = 0;
|
||||
}
|
||||
|
||||
if (burg.cell >= cells.i.length) {
|
||||
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to invalid cell", burg.cell);
|
||||
ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to invalid cell", burg.cell);
|
||||
burg.cell = findCell(burg.x, burg.y);
|
||||
cells.i.filter(i => cells.burg[i] === burg.i).forEach(i => (cells.burg[i] = 0));
|
||||
cells.burg[burg.cell] = burg.i;
|
||||
}
|
||||
|
||||
if (burg.state && !pack.states[burg.state]) {
|
||||
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to invalid state", burg.state);
|
||||
ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to invalid state", burg.state);
|
||||
burg.state = 0;
|
||||
}
|
||||
|
||||
if (burg.state && pack.states[burg.state].removed) {
|
||||
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to removed state", burg.state);
|
||||
ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to removed state", burg.state);
|
||||
burg.state = 0;
|
||||
}
|
||||
|
||||
if (burg.state === undefined) {
|
||||
ERROR && console.error("Data Integrity Check. Burg", burg.i, "has no state data");
|
||||
ERROR && console.error("Data integrity check. Burg", burg.i, "has no state data");
|
||||
burg.state = 0;
|
||||
}
|
||||
});
|
||||
|
||||
pack.states.forEach(state => {
|
||||
if (state.removed) return;
|
||||
|
||||
const stateBurgs = pack.burgs.filter(b => b.state === state.i && !b.removed);
|
||||
const capitalBurgs = stateBurgs.filter(b => b.capital);
|
||||
|
||||
if (!state.i && capitalBurgs.length) {
|
||||
ERROR &&
|
||||
console.error(
|
||||
`Data integrity check. Neutral burgs (${capitalBurgs
|
||||
.map(b => b.i)
|
||||
.join(", ")}) marked as capitals. Moving them to towns`
|
||||
);
|
||||
|
||||
capitalBurgs.forEach(burg => {
|
||||
burg.capital = 0;
|
||||
moveBurgToGroup(burg.i, "towns");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (capitalBurgs.length > 1) {
|
||||
const message = `Data integrity check. State ${state.i} has multiple capitals (${capitalBurgs
|
||||
.map(b => b.i)
|
||||
.join(", ")}) assigned. Keeping the first as capital and moving others to towns`;
|
||||
ERROR && console.error(message);
|
||||
|
||||
capitalBurgs.forEach((burg, i) => {
|
||||
if (!i) return;
|
||||
burg.capital = 0;
|
||||
moveBurgToGroup(burg.i, "towns");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.i && stateBurgs.length && !capitalBurgs.length) {
|
||||
ERROR &&
|
||||
console.error(`Data integrity check. State ${state.i} has no capital. Assigning the first burg as capital`);
|
||||
stateBurgs[0].capital = 1;
|
||||
moveBurgToGroup(stateBurgs[0].i, "cities");
|
||||
}
|
||||
});
|
||||
|
||||
pack.provinces.forEach(p => {
|
||||
if (!p.i || p.removed) return;
|
||||
if (pack.states[p.state] && !pack.states[p.state].removed) return;
|
||||
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state);
|
||||
ERROR && console.error("Data integrity check. Province", p.i, "is linked to removed state", p.state);
|
||||
p.removed = true; // remove incorrect province
|
||||
});
|
||||
|
||||
|
|
@ -586,7 +652,7 @@ async function parseLoadedData(data) {
|
|||
|
||||
pack.markers.forEach(marker => {
|
||||
if (markerIds[marker.i]) {
|
||||
ERROR && console.error("Data Integrity Check. Marker", marker.i, "has non-unique id. Changing to", nextId);
|
||||
ERROR && console.error("Data integrity check. Marker", marker.i, "has non-unique id. Changing to", nextId);
|
||||
|
||||
const domElements = document.querySelectorAll("#marker" + marker.i);
|
||||
if (domElements[1]) domElements[1].id = "marker" + nextId; // rename 2nd dom element
|
||||
|
|
@ -606,7 +672,7 @@ async function parseLoadedData(data) {
|
|||
}
|
||||
})();
|
||||
|
||||
changeMapSize();
|
||||
fitMapToScreen();
|
||||
|
||||
// remove href from emblems, to trigger rendering on load
|
||||
emblems.selectAll("use").attr("href", null);
|
||||
|
|
@ -627,7 +693,7 @@ async function parseLoadedData(data) {
|
|||
ERROR && console.error(error);
|
||||
clearMainTip();
|
||||
|
||||
alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br />generate a new random map or cancel the loading
|
||||
alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br>generate a new random map or cancel the loading.<br>Map version: ${mapVersion}. Generator version: ${version}.
|
||||
<p id="errorBox">${parseError(error)}</p>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@ function prepareMapData() {
|
|||
heightUnit.value,
|
||||
heightExponentInput.value,
|
||||
temperatureScale.value,
|
||||
barSizeInput.value,
|
||||
barLabel.value,
|
||||
barBackOpacity.value,
|
||||
barBackColor.value,
|
||||
barPosX.value,
|
||||
barPosY.value,
|
||||
"", // previously used for barSize.value
|
||||
"", // previously used for barLabel.value
|
||||
"", // previously used for barBackColor.value
|
||||
"", // previously used for barBackColor.value
|
||||
"", // previously used for barPosX.value
|
||||
"", // previously used for barPosY.value
|
||||
populationRate,
|
||||
urbanization,
|
||||
mapSizeOutput.value,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,18 @@ window.Military = (function () {
|
|||
|
||||
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
|
||||
const area = d3.sum(valid.map(s => s.area)); // total area
|
||||
const rate = {x: 0, Ally: -0.2, Friendly: -0.1, Neutral: 0, Suspicion: 0.1, Enemy: 1, Unknown: 0, Rival: 0.5, Vassal: 0.5, Suzerain: -0.5};
|
||||
const rate = {
|
||||
x: 0,
|
||||
Ally: -0.2,
|
||||
Friendly: -0.1,
|
||||
Neutral: 0,
|
||||
Suspicion: 0.1,
|
||||
Enemy: 1,
|
||||
Unknown: 0,
|
||||
Rival: 0.5,
|
||||
Vassal: 0.5,
|
||||
Suzerain: -0.5
|
||||
};
|
||||
|
||||
const stateModifier = {
|
||||
melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1},
|
||||
|
|
@ -24,14 +35,59 @@ window.Military = (function () {
|
|||
};
|
||||
|
||||
const cellTypeModifier = {
|
||||
nomadic: {melee: 0.2, ranged: 0.5, mounted: 3, machinery: 0.4, naval: 0.3, armored: 1.6, aviation: 1, magical: 0.5},
|
||||
wetland: {melee: 0.8, ranged: 2, mounted: 0.3, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5},
|
||||
highland: {melee: 1.2, ranged: 1.6, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
|
||||
nomadic: {
|
||||
melee: 0.2,
|
||||
ranged: 0.5,
|
||||
mounted: 3,
|
||||
machinery: 0.4,
|
||||
naval: 0.3,
|
||||
armored: 1.6,
|
||||
aviation: 1,
|
||||
magical: 0.5
|
||||
},
|
||||
wetland: {
|
||||
melee: 0.8,
|
||||
ranged: 2,
|
||||
mounted: 0.3,
|
||||
machinery: 1.2,
|
||||
naval: 1.0,
|
||||
armored: 0.2,
|
||||
aviation: 0.5,
|
||||
magical: 0.5
|
||||
},
|
||||
highland: {
|
||||
melee: 1.2,
|
||||
ranged: 1.6,
|
||||
mounted: 0.3,
|
||||
machinery: 3,
|
||||
naval: 1.0,
|
||||
armored: 0.8,
|
||||
aviation: 0.3,
|
||||
magical: 2
|
||||
}
|
||||
};
|
||||
|
||||
const burgTypeModifier = {
|
||||
nomadic: {melee: 0.3, ranged: 0.8, mounted: 3, machinery: 0.4, naval: 1.0, armored: 1.6, aviation: 1, magical: 0.5},
|
||||
wetland: {melee: 1, ranged: 1.6, mounted: 0.2, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5},
|
||||
nomadic: {
|
||||
melee: 0.3,
|
||||
ranged: 0.8,
|
||||
mounted: 3,
|
||||
machinery: 0.4,
|
||||
naval: 1.0,
|
||||
armored: 1.6,
|
||||
aviation: 1,
|
||||
magical: 0.5
|
||||
},
|
||||
wetland: {
|
||||
melee: 1,
|
||||
ranged: 1.6,
|
||||
mounted: 0.2,
|
||||
machinery: 1.2,
|
||||
naval: 1.0,
|
||||
armored: 0.2,
|
||||
aviation: 0.5,
|
||||
magical: 0.5
|
||||
},
|
||||
highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
|
||||
};
|
||||
|
||||
|
|
@ -40,8 +96,16 @@ window.Military = (function () {
|
|||
const d = s.diplomacy;
|
||||
|
||||
const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized
|
||||
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness
|
||||
const neighborsRateRaw = s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5);
|
||||
const diplomacyRate = d.some(d => d === "Enemy")
|
||||
? 1
|
||||
: d.some(d => d === "Rival")
|
||||
? 0.8
|
||||
: d.some(d => d === "Suspicion")
|
||||
? 0.5
|
||||
: 0.1; // peacefulness
|
||||
const neighborsRateRaw = s.neighbors
|
||||
.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion"))
|
||||
.reduce((s, r) => (s += rate[r]), 0.5);
|
||||
const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate
|
||||
s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier)
|
||||
s.temp.platoons = [];
|
||||
|
|
@ -86,8 +150,10 @@ window.Military = (function () {
|
|||
|
||||
let modifier = cells.pop[i] / 100; // basic rural army in percentages
|
||||
if (culture !== stateObj.culture) modifier = stateObj.form === "Union" ? modifier / 1.2 : modifier / 2; // non-dominant culture
|
||||
if (religion !== cells.religion[stateObj.center]) modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion
|
||||
if (cells.f[i] !== cells.f[stateObj.center]) modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass
|
||||
if (religion !== cells.religion[stateObj.center])
|
||||
modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion
|
||||
if (cells.f[i] !== cells.f[stateObj.center])
|
||||
modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass
|
||||
const type = getType(i);
|
||||
|
||||
for (const unit of options.military) {
|
||||
|
|
@ -111,7 +177,17 @@ window.Military = (function () {
|
|||
n = 1;
|
||||
}
|
||||
|
||||
stateObj.temp.platoons.push({cell: i, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
|
||||
stateObj.temp.platoons.push({
|
||||
cell: i,
|
||||
a: total,
|
||||
t: total,
|
||||
x,
|
||||
y,
|
||||
u: unit.name,
|
||||
n,
|
||||
s: unit.separate,
|
||||
type: unit.type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +229,17 @@ window.Military = (function () {
|
|||
n = 1;
|
||||
}
|
||||
|
||||
stateObj.temp.platoons.push({cell: b.cell, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
|
||||
stateObj.temp.platoons.push({
|
||||
cell: b.cell,
|
||||
a: total,
|
||||
t: total,
|
||||
x,
|
||||
y,
|
||||
u: unit.name,
|
||||
n,
|
||||
s: unit.separate,
|
||||
type: unit.type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +347,8 @@ window.Military = (function () {
|
|||
const army = armies
|
||||
.append("g")
|
||||
.attr("id", "army" + s)
|
||||
.attr("fill", baseColor);
|
||||
.attr("fill", baseColor)
|
||||
.attr("color", darkerColor);
|
||||
|
||||
const g = army
|
||||
.selectAll("g")
|
||||
|
|
@ -282,7 +369,7 @@ window.Military = (function () {
|
|||
.attr("y", d => d.y)
|
||||
.text(d => getTotal(d));
|
||||
g.append("rect")
|
||||
.attr("fill", darkerColor)
|
||||
.attr("fill", "currentColor")
|
||||
.attr("x", d => x(d) - h)
|
||||
.attr("y", d => y(d))
|
||||
.attr("width", h)
|
||||
|
|
@ -294,33 +381,34 @@ window.Military = (function () {
|
|||
.text(d => d.icon);
|
||||
};
|
||||
|
||||
const drawRegiment = function (reg, s) {
|
||||
const drawRegiment = function (reg, stateId) {
|
||||
const size = +armies.attr("box-size");
|
||||
const w = reg.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x1 = rn(reg.x - w / 2, 2);
|
||||
const y1 = rn(reg.y - size, 2);
|
||||
|
||||
let army = armies.select("g#army" + s);
|
||||
let army = armies.select("g#army" + stateId);
|
||||
if (!army.size()) {
|
||||
const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999";
|
||||
const baseColor = pack.states[stateId].color[0] === "#" ? pack.states[stateId].color : "#999";
|
||||
const darkerColor = d3.color(baseColor).darker().hex();
|
||||
army = armies
|
||||
.append("g")
|
||||
.attr("id", "army" + s)
|
||||
.attr("fill", baseColor);
|
||||
.attr("id", "army" + stateId)
|
||||
.attr("fill", baseColor)
|
||||
.attr("color", darkerColor);
|
||||
}
|
||||
const darkerColor = d3.color(army.attr("fill")).darker().hex();
|
||||
|
||||
const g = army
|
||||
.append("g")
|
||||
.attr("id", "regiment" + s + "-" + reg.i)
|
||||
.attr("id", "regiment" + stateId + "-" + reg.i)
|
||||
.attr("data-name", reg.name)
|
||||
.attr("data-state", s)
|
||||
.attr("data-state", stateId)
|
||||
.attr("data-id", reg.i);
|
||||
g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h);
|
||||
g.append("text").attr("x", reg.x).attr("y", reg.y).text(getTotal(reg));
|
||||
g.append("rect")
|
||||
.attr("fill", darkerColor)
|
||||
.attr("fill", "currentColor")
|
||||
.attr("x", x1 - h)
|
||||
.attr("y", y1)
|
||||
.attr("width", h)
|
||||
|
|
@ -379,7 +467,13 @@ window.Military = (function () {
|
|||
// get default regiment emblem
|
||||
const getEmblem = function (r) {
|
||||
if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops
|
||||
if (!r.n && pack.states[r.state].form === "Monarchy" && pack.cells.burg[r.cell] && pack.burgs[pack.cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital
|
||||
if (
|
||||
!r.n &&
|
||||
pack.states[r.state].form === "Monarchy" &&
|
||||
pack.cells.burg[r.cell] &&
|
||||
pack.burgs[pack.cells.burg[r.cell]].capital
|
||||
)
|
||||
return "👑"; // "Royal" regiment based in capital
|
||||
const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment
|
||||
const unit = options.military.find(u => u.name === mainUnit);
|
||||
return unit.icon;
|
||||
|
|
@ -400,7 +494,9 @@ window.Military = (function () {
|
|||
.map(t => `— ${t}: ${r.u[t]}`)
|
||||
.join("\r\n")
|
||||
: null;
|
||||
const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : "";
|
||||
const troops = composition
|
||||
? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.`
|
||||
: "";
|
||||
|
||||
const campaign = s.campaigns ? ra(s.campaigns) : null;
|
||||
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6);
|
||||
|
|
@ -409,5 +505,16 @@ window.Military = (function () {
|
|||
notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend});
|
||||
};
|
||||
|
||||
return {generate, redraw, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, moveRegiment, getTotal, getEmblem};
|
||||
return {
|
||||
generate,
|
||||
redraw,
|
||||
getDefaultOptions,
|
||||
getName,
|
||||
generateNote,
|
||||
drawRegiments,
|
||||
drawRegiment,
|
||||
moveRegiment,
|
||||
getTotal,
|
||||
getEmblem
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -368,11 +368,11 @@ window.Religions = (function () {
|
|||
Totemism: 1
|
||||
},
|
||||
Organized: {
|
||||
Polytheism: 14,
|
||||
Monotheism: 12,
|
||||
Dualism: 6,
|
||||
Pantheism: 6,
|
||||
"Non-theism": 4
|
||||
Polytheism: 7,
|
||||
Monotheism: 7,
|
||||
Dualism: 3,
|
||||
Pantheism: 2,
|
||||
"Non-theism": 2
|
||||
},
|
||||
Cult: {
|
||||
Cult: 5,
|
||||
|
|
@ -418,12 +418,13 @@ window.Religions = (function () {
|
|||
Shamanism: {Beliefs: 3, Shamanism: 2, Druidism: 1, Spirits: 1},
|
||||
Animism: {Spirits: 3, Beliefs: 1},
|
||||
Polytheism: {Deities: 3, Faith: 1, Gods: 1, Pantheon: 1},
|
||||
"Ancestor worship": {Beliefs: 1, Forefathers: 2, Ancestors: 2},
|
||||
"Ancestor Worship": {Beliefs: 1, Forefathers: 2, Ancestors: 2},
|
||||
"Nature Worship": {Beliefs: 3, Druids: 1},
|
||||
Totemism: {Beliefs: 2, Totems: 2, Idols: 1},
|
||||
|
||||
Monotheism: {Religion: 2, Church: 3, Faith: 1},
|
||||
Dualism: {Religion: 3, Faith: 1, Cult: 1},
|
||||
Pantheism: {Religion: 1, Faith: 1},
|
||||
"Non-theism": {Beliefs: 3, Spirits: 1},
|
||||
|
||||
Cult: {Cult: 4, Sect: 2, Arcanum: 1, Order: 1, Worship: 1},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
function drawStateLabels(list) {
|
||||
console.time("drawStateLabels");
|
||||
|
||||
// temporary make the labels visible
|
||||
const layerDisplay = labels.style("display");
|
||||
labels.style("display", null);
|
||||
|
||||
const {cells, states, features} = pack;
|
||||
const stateIds = cells.state;
|
||||
|
||||
|
|
@ -17,7 +21,11 @@ function drawStateLabels(list) {
|
|||
const MAX_ITERATIONS = 100;
|
||||
|
||||
const labelPaths = getLabelPaths();
|
||||
drawLabelPath();
|
||||
const letterLength = checkExampleLetterLength();
|
||||
drawLabelPath(letterLength);
|
||||
|
||||
// restore labels visibility
|
||||
labels.style("display", layerDisplay);
|
||||
|
||||
function getLabelPaths() {
|
||||
const labelPaths = [];
|
||||
|
|
@ -110,17 +118,22 @@ function drawStateLabels(list) {
|
|||
}
|
||||
}
|
||||
|
||||
function drawLabelPath() {
|
||||
function checkExampleLetterLength() {
|
||||
const textGroup = d3.select("g#labels > g#states");
|
||||
const testLabel = textGroup.append("text").attr("x", 0).attr("y", 0).text("Example");
|
||||
const letterLength = testLabel.node().getComputedTextLength() / 7; // approximate length of 1 letter
|
||||
testLabel.remove();
|
||||
|
||||
return letterLength;
|
||||
}
|
||||
|
||||
function drawLabelPath(letterLength) {
|
||||
const mode = options.stateLabelsMode || "auto";
|
||||
const lineGen = d3.line().curve(d3.curveBundle.beta(1));
|
||||
|
||||
const textGroup = d3.select("g#labels > g#states");
|
||||
const pathGroup = d3.select("defs > g#deftemp > g#textPaths");
|
||||
|
||||
const testLabel = textGroup.append("text").attr("x", 0).attr("y", 0).text("Example");
|
||||
const letterLength = testLabel.node().getComputedTextLength() / 7; // approximate length of 1 letter
|
||||
testLabel.remove();
|
||||
|
||||
for (const [stateId, pathPoints] of labelPaths) {
|
||||
const state = states[stateId];
|
||||
if (!state.i || state.removed) throw new Error("State must not be neutral or removed");
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ window.Submap = (function () {
|
|||
|
||||
const projection = options.projection;
|
||||
const inverse = options.inverse;
|
||||
const stage = s => INFO && console.log("SUBMAP:", s);
|
||||
const stage = s => INFO && console.info("SUBMAP:", s);
|
||||
const timeStart = performance.now();
|
||||
invokeActiveZooming();
|
||||
|
||||
|
|
@ -36,13 +36,14 @@ window.Submap = (function () {
|
|||
seed = parentMap.seed;
|
||||
Math.random = aleaPRNG(seed);
|
||||
INFO && console.group("SubMap with seed: " + seed);
|
||||
DEBUG && console.log("Using Options:", options);
|
||||
DEBUG && console.info("Using Options:", options);
|
||||
|
||||
// create new grid
|
||||
applyMapSize();
|
||||
applyGraphSize();
|
||||
grid = generateGrid();
|
||||
|
||||
drawScaleBar(scale);
|
||||
drawScaleBar(scaleBar, scale);
|
||||
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||
|
||||
const resampler = (points, qtree, f) => {
|
||||
for (const [i, [x, y]] of points.entries()) {
|
||||
|
|
@ -395,7 +396,7 @@ window.Submap = (function () {
|
|||
b.removed = true;
|
||||
return;
|
||||
}
|
||||
DEBUG && console.log(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`);
|
||||
DEBUG && console.info(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`);
|
||||
[b.x, b.y] = b.port ? getMiddlePoint(newCell, neighbor) : cells.p[newCell];
|
||||
if (b.port) b.port = cells.f[neighbor]; // copy feature number
|
||||
b.cell = newCell;
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
window.ThreeD = (function () {
|
||||
const options = {
|
||||
scale: 50,
|
||||
lightness: 0.7,
|
||||
lightness: 0.6,
|
||||
shadow: 0.5,
|
||||
sun: {x: 100, y: 600, z: 1000},
|
||||
sun: {x: 100, y: 800, z: 1000},
|
||||
rotateMesh: 0,
|
||||
rotateGlobe: 0.5,
|
||||
skyColor: "#9ecef5",
|
||||
waterColor: "#466eab",
|
||||
sunColor: "#cccccc",
|
||||
extendedWater: 0,
|
||||
labels3d: 0,
|
||||
wireframe: 0,
|
||||
resolution: 2,
|
||||
resolutionScale: 2048,
|
||||
sunColor: "#cccccc",
|
||||
subdivide: 0
|
||||
};
|
||||
|
||||
|
|
@ -202,16 +202,16 @@ window.ThreeD = (function () {
|
|||
};
|
||||
|
||||
const saveOBJ = async function () {
|
||||
downloadFile(await getOBJ(), getFileName() + ".obj", "text/plain;charset=UTF-8");
|
||||
const objexporter = await OBJExporter();
|
||||
const obj = await objexporter.parse(mesh);
|
||||
|
||||
downloadFile(obj, getFileName() + ".obj", "text/plain;charset=UTF-8");
|
||||
};
|
||||
|
||||
// start 3d view and heightmap edit preview
|
||||
async function newMesh(canvas) {
|
||||
const loaded = await loadTHREE();
|
||||
if (!loaded) {
|
||||
tip("Cannot load 3d library", false, "error", 4000);
|
||||
return false;
|
||||
}
|
||||
if (!loaded) return tip("Cannot load 3d library", false, "error", 4000);
|
||||
|
||||
scene = new THREE.Scene();
|
||||
|
||||
|
|
@ -221,17 +221,16 @@ window.ThreeD = (function () {
|
|||
spotLight = new THREE.SpotLight(options.sunColor, 0.8, 2000, 0.8, 0, 0);
|
||||
spotLight.position.set(options.sun.x, options.sun.y, options.sun.z);
|
||||
spotLight.castShadow = true;
|
||||
//maybe add a option for this. But changing the option will require to reinstance the spotLight.
|
||||
spotLight.shadow.mapSize.width = 2048;
|
||||
spotLight.shadow.mapSize.height = 2048;
|
||||
scene.add(spotLight);
|
||||
//scene.add(new THREE.SpotLightHelper(spotLight));
|
||||
// scene.add(new THREE.SpotLightHelper(spotLight));
|
||||
|
||||
// Renderer
|
||||
Renderer = new THREE.WebGLRenderer({canvas, antialias: true, preserveDrawingBuffer: true});
|
||||
Renderer.setSize(canvas.width, canvas.height);
|
||||
Renderer.shadowMap.enabled = true;
|
||||
// Renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
Renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
if (options.extendedWater) extendWater(graphWidth, graphHeight);
|
||||
createMesh(graphWidth, graphHeight, grid.cellsX, grid.cellsY);
|
||||
|
||||
|
|
@ -241,8 +240,11 @@ window.ThreeD = (function () {
|
|||
|
||||
// controls
|
||||
controls = await OrbitControls(camera, canvas);
|
||||
controls.enableKeys = false;
|
||||
controls.minDistance = 10;
|
||||
controls.listenToKeyEvents(window);
|
||||
controls.zoomSpeed = 0.25;
|
||||
|
||||
controls.panSpeed = 0.5;
|
||||
controls.minDistance = 100;
|
||||
controls.maxDistance = 1000;
|
||||
controls.maxPolarAngle = Math.PI / 2;
|
||||
controls.autoRotate = Boolean(options.rotateMesh);
|
||||
|
|
@ -250,7 +252,6 @@ window.ThreeD = (function () {
|
|||
if (controls.autoRotate) animate();
|
||||
|
||||
controls.addEventListener("change", render);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -440,12 +441,11 @@ window.ThreeD = (function () {
|
|||
|
||||
async function createMeshTextureUrl() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mapOptions = {
|
||||
const url = await getMapURL("mesh", {
|
||||
noLabels: options.labels3d,
|
||||
noWater: options.extendedWater,
|
||||
fullMap: true
|
||||
};
|
||||
const url = await getMapURL("mesh", mapOptions);
|
||||
});
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = options.resolutionScale;
|
||||
|
|
@ -541,11 +541,6 @@ window.ThreeD = (function () {
|
|||
|
||||
async function update3dTexture() {
|
||||
if (texture) texture.dispose();
|
||||
const mapOptions = {
|
||||
noLabels: options.labels3d,
|
||||
noWater: options.extendedWater,
|
||||
fullMap: true
|
||||
};
|
||||
const url = await createMeshTextureUrl();
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 4000);
|
||||
texture = new THREE.TextureLoader().load(url, render);
|
||||
|
|
@ -580,7 +575,7 @@ window.ThreeD = (function () {
|
|||
|
||||
// controls
|
||||
controls = await OrbitControls(camera, Renderer.domElement);
|
||||
controls.enableKeys = false;
|
||||
controls.zoomSpeed = 0.25;
|
||||
controls.minDistance = 1.8;
|
||||
controls.maxDistance = 10;
|
||||
controls.autoRotate = Boolean(options.rotateGlobe);
|
||||
|
|
@ -628,13 +623,7 @@ window.ThreeD = (function () {
|
|||
material.map = texture;
|
||||
if (addMesh) addGlobe3dMesh();
|
||||
};
|
||||
img2.src = await getMapURL("mesh", {globe: true, fullMap: true});
|
||||
}
|
||||
|
||||
async function getOBJ() {
|
||||
const objexporter = await OBJExporter();
|
||||
const data = await objexporter.parse(mesh);
|
||||
return data;
|
||||
img2.src = await getMapURL("mesh", {noScaleBar: true, fullMap: true});
|
||||
}
|
||||
|
||||
function addGlobe3dMesh() {
|
||||
|
|
|
|||
|
|
@ -21,38 +21,37 @@ function editBurg(id) {
|
|||
modules.editBurg = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection);
|
||||
document.getElementById("burgGroupHide").addEventListener("click", hideGroupSection);
|
||||
document.getElementById("burgSelectGroup").addEventListener("change", changeGroup);
|
||||
document.getElementById("burgInputGroup").addEventListener("change", createNewGroup);
|
||||
document.getElementById("burgAddGroup").addEventListener("click", toggleNewGroupInput);
|
||||
document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup);
|
||||
byId("burgGroupShow").addEventListener("click", showGroupSection);
|
||||
byId("burgGroupHide").addEventListener("click", hideGroupSection);
|
||||
byId("burgSelectGroup").addEventListener("change", changeGroup);
|
||||
byId("burgInputGroup").addEventListener("change", createNewGroup);
|
||||
byId("burgAddGroup").addEventListener("click", toggleNewGroupInput);
|
||||
byId("burgRemoveGroup").addEventListener("click", removeBurgsGroup);
|
||||
|
||||
document.getElementById("burgName").addEventListener("input", changeName);
|
||||
document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom);
|
||||
document.getElementById("burgType").addEventListener("input", changeType);
|
||||
document.getElementById("burgCulture").addEventListener("input", changeCulture);
|
||||
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
document.getElementById("burgPopulation").addEventListener("change", changePopulation);
|
||||
byId("burgName").addEventListener("input", changeName);
|
||||
byId("burgNameReRandom").addEventListener("click", generateNameRandom);
|
||||
byId("burgType").addEventListener("input", changeType);
|
||||
byId("burgCulture").addEventListener("input", changeCulture);
|
||||
byId("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
byId("burgPopulation").addEventListener("change", changePopulation);
|
||||
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
|
||||
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
|
||||
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
|
||||
document.getElementById("addCustomMFCGBurgLink").addEventListener("click", addCustomMfcgLink);
|
||||
byId("burgLinkOpen").addEventListener("click", openBurgLink);
|
||||
byId("burgLinkEdit").addEventListener("click", changeBurgLink);
|
||||
|
||||
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
|
||||
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
|
||||
document.getElementById("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle);
|
||||
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
|
||||
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
|
||||
byId("burgStyleShow").addEventListener("click", showStyleSection);
|
||||
byId("burgStyleHide").addEventListener("click", hideStyleSection);
|
||||
byId("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle);
|
||||
byId("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
|
||||
byId("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
|
||||
|
||||
document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgToggleMFCGMap").addEventListener("click", toggleMFCGMap);
|
||||
document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg);
|
||||
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
|
||||
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
|
||||
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
|
||||
document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
|
||||
byId("burgEmblem").addEventListener("click", openEmblemEdit);
|
||||
byId("burgTogglePreview").addEventListener("click", toggleBurgPreview);
|
||||
byId("burgEditEmblem").addEventListener("click", openEmblemEdit);
|
||||
byId("burgRelocate").addEventListener("click", toggleRelocateBurg);
|
||||
byId("burglLegend").addEventListener("click", editBurgLegend);
|
||||
byId("burgLock").addEventListener("click", toggleBurgLockButton);
|
||||
byId("burgRemove").addEventListener("click", removeSelectedBurg);
|
||||
byId("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
|
||||
|
||||
function updateBurgValues() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
|
|
@ -60,46 +59,46 @@ function editBurg(id) {
|
|||
const province = pack.cells.province[b.cell];
|
||||
const provinceName = province ? pack.provinces[province].fullName + ", " : "";
|
||||
const stateName = pack.states[b.state].fullName || pack.states[b.state].name;
|
||||
document.getElementById("burgProvinceAndState").innerHTML = provinceName + stateName;
|
||||
byId("burgProvinceAndState").innerHTML = provinceName + stateName;
|
||||
|
||||
document.getElementById("burgName").value = b.name;
|
||||
document.getElementById("burgType").value = b.type || "Generic";
|
||||
document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization);
|
||||
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
|
||||
byId("burgName").value = b.name;
|
||||
byId("burgType").value = b.type || "Generic";
|
||||
byId("burgPopulation").value = rn(b.population * populationRate * urbanization);
|
||||
byId("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
|
||||
|
||||
// update list and select culture
|
||||
const cultureSelect = document.getElementById("burgCulture");
|
||||
const cultureSelect = byId("burgCulture");
|
||||
cultureSelect.options.length = 0;
|
||||
const cultures = pack.cultures.filter(c => !c.removed);
|
||||
cultures.forEach(c => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture)));
|
||||
|
||||
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
|
||||
document.getElementById("burgTemperature").innerHTML = convertTemperature(temperature);
|
||||
document.getElementById("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature);
|
||||
document.getElementById("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]);
|
||||
byId("burgTemperature").innerHTML = convertTemperature(temperature);
|
||||
byId("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature);
|
||||
byId("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]);
|
||||
|
||||
// toggle features
|
||||
if (b.capital) document.getElementById("burgCapital").classList.remove("inactive");
|
||||
else document.getElementById("burgCapital").classList.add("inactive");
|
||||
if (b.port) document.getElementById("burgPort").classList.remove("inactive");
|
||||
else document.getElementById("burgPort").classList.add("inactive");
|
||||
if (b.citadel) document.getElementById("burgCitadel").classList.remove("inactive");
|
||||
else document.getElementById("burgCitadel").classList.add("inactive");
|
||||
if (b.walls) document.getElementById("burgWalls").classList.remove("inactive");
|
||||
else document.getElementById("burgWalls").classList.add("inactive");
|
||||
if (b.plaza) document.getElementById("burgPlaza").classList.remove("inactive");
|
||||
else document.getElementById("burgPlaza").classList.add("inactive");
|
||||
if (b.temple) document.getElementById("burgTemple").classList.remove("inactive");
|
||||
else document.getElementById("burgTemple").classList.add("inactive");
|
||||
if (b.shanty) document.getElementById("burgShanty").classList.remove("inactive");
|
||||
else document.getElementById("burgShanty").classList.add("inactive");
|
||||
if (b.capital) byId("burgCapital").classList.remove("inactive");
|
||||
else byId("burgCapital").classList.add("inactive");
|
||||
if (b.port) byId("burgPort").classList.remove("inactive");
|
||||
else byId("burgPort").classList.add("inactive");
|
||||
if (b.citadel) byId("burgCitadel").classList.remove("inactive");
|
||||
else byId("burgCitadel").classList.add("inactive");
|
||||
if (b.walls) byId("burgWalls").classList.remove("inactive");
|
||||
else byId("burgWalls").classList.add("inactive");
|
||||
if (b.plaza) byId("burgPlaza").classList.remove("inactive");
|
||||
else byId("burgPlaza").classList.add("inactive");
|
||||
if (b.temple) byId("burgTemple").classList.remove("inactive");
|
||||
else byId("burgTemple").classList.add("inactive");
|
||||
if (b.shanty) byId("burgShanty").classList.remove("inactive");
|
||||
else byId("burgShanty").classList.add("inactive");
|
||||
|
||||
//toggle lock
|
||||
updateBurgLockIcon();
|
||||
|
||||
// select group
|
||||
const group = elSelected.node().parentNode.id;
|
||||
const select = document.getElementById("burgSelectGroup");
|
||||
const select = byId("burgSelectGroup");
|
||||
select.options.length = 0; // remove all options
|
||||
|
||||
burgLabels.selectAll("g").each(function () {
|
||||
|
|
@ -109,68 +108,16 @@ function editBurg(id) {
|
|||
// set emlem image
|
||||
const coaID = "burgCOA" + id;
|
||||
COArenderer.trigger(coaID, b.coa);
|
||||
document.getElementById("burgEmblem").setAttribute("href", "#" + coaID);
|
||||
byId("burgEmblem").setAttribute("href", "#" + coaID);
|
||||
|
||||
if (options.showMFCGMap) {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "block";
|
||||
updateMFCGFrame(b);
|
||||
|
||||
if (b.link) {
|
||||
document.getElementById("mfcgBurgSeedSection").style.display = "none";
|
||||
} else {
|
||||
document.getElementById("mfcgBurgSeedSection").style.display = "inline-block";
|
||||
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
|
||||
}
|
||||
if (options.showBurgPreview) {
|
||||
byId("burgPreviewSection").style.display = "block";
|
||||
updateBurgPreview(b);
|
||||
} else {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "none";
|
||||
byId("burgPreviewSection").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
|
||||
function getTemperatureLikeness(temperature) {
|
||||
if (temperature < -5) return "Yakutsk";
|
||||
const cities = [
|
||||
"Snag (Yukon)",
|
||||
"Yellowknife (Canada)",
|
||||
"Okhotsk (Russia)",
|
||||
"Fairbanks (Alaska)",
|
||||
"Nuuk (Greenland)",
|
||||
"Murmansk", // -5 - 0
|
||||
"Arkhangelsk",
|
||||
"Anchorage",
|
||||
"Tromsø",
|
||||
"Reykjavik",
|
||||
"Riga",
|
||||
"Stockholm",
|
||||
"Halifax",
|
||||
"Prague",
|
||||
"Copenhagen",
|
||||
"London", // 1 - 10
|
||||
"Antwerp",
|
||||
"Paris",
|
||||
"Milan",
|
||||
"Batumi",
|
||||
"Rome",
|
||||
"Dubrovnik",
|
||||
"Lisbon",
|
||||
"Barcelona",
|
||||
"Marrakesh",
|
||||
"Alexandria", // 11 - 20
|
||||
"Tegucigalpa",
|
||||
"Guangzhou",
|
||||
"Rio de Janeiro",
|
||||
"Dakar",
|
||||
"Miami",
|
||||
"Jakarta",
|
||||
"Mogadishu",
|
||||
"Bangkok",
|
||||
"Aden",
|
||||
"Khartoum"
|
||||
]; // 21 - 30
|
||||
if (temperature > 30) return "Mecca";
|
||||
return cities[temperature + 5] || null;
|
||||
}
|
||||
|
||||
function dragBurgLabel() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
|
|
@ -186,15 +133,15 @@ function editBurg(id) {
|
|||
|
||||
function showGroupSection() {
|
||||
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("burgGroupSection").style.display = "inline-block";
|
||||
byId("burgGroupSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("burgGroupSection").style.display = "none";
|
||||
document.getElementById("burgInputGroup").style.display = "none";
|
||||
document.getElementById("burgInputGroup").value = "";
|
||||
document.getElementById("burgSelectGroup").style.display = "inline-block";
|
||||
byId("burgGroupSection").style.display = "none";
|
||||
byId("burgInputGroup").style.display = "none";
|
||||
byId("burgInputGroup").value = "";
|
||||
byId("burgSelectGroup").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
|
|
@ -223,7 +170,7 @@ function editBurg(id) {
|
|||
.replace(/ /g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (document.getElementById(group)) {
|
||||
if (byId(group)) {
|
||||
tip("Element with this id already exists. Please provide a unique name", false, "error");
|
||||
return;
|
||||
}
|
||||
|
|
@ -251,10 +198,10 @@ function editBurg(id) {
|
|||
// just rename if only 1 element left
|
||||
const count = elSelected.node().parentNode.childElementCount;
|
||||
if (oldGroup !== "cities" && oldGroup !== "towns" && count === 1) {
|
||||
document.getElementById("burgSelectGroup").selectedOptions[0].remove();
|
||||
document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true));
|
||||
byId("burgSelectGroup").selectedOptions[0].remove();
|
||||
byId("burgSelectGroup").options.add(new Option(group, group, false, true));
|
||||
toggleNewGroupInput();
|
||||
document.getElementById("burgInputGroup").value = "";
|
||||
byId("burgInputGroup").value = "";
|
||||
labelG.id = group;
|
||||
iconG.id = group;
|
||||
if (anchor) anchorG.id = group;
|
||||
|
|
@ -262,9 +209,9 @@ function editBurg(id) {
|
|||
}
|
||||
|
||||
// create new groups
|
||||
document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true));
|
||||
byId("burgSelectGroup").options.add(new Option(group, group, false, true));
|
||||
toggleNewGroupInput();
|
||||
document.getElementById("burgInputGroup").value = "";
|
||||
byId("burgInputGroup").value = "";
|
||||
|
||||
addBurgsGroup(group);
|
||||
moveBurgToGroup(id, group);
|
||||
|
|
@ -284,7 +231,9 @@ function editBurg(id) {
|
|||
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
|
||||
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
|
||||
}?
|
||||
<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${burgsToRemove.length}`;
|
||||
<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${
|
||||
burgsToRemove.length
|
||||
}`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove burg group",
|
||||
|
|
@ -343,7 +292,10 @@ function editBurg(id) {
|
|||
|
||||
function changePopulation() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
|
||||
pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4);
|
||||
updateBurgPreview(burg);
|
||||
}
|
||||
|
||||
function toggleFeature() {
|
||||
|
|
@ -357,9 +309,9 @@ function editBurg(id) {
|
|||
if (burg[feature]) this.classList.remove("inactive");
|
||||
else if (!burg[feature]) this.classList.add("inactive");
|
||||
|
||||
if (burg.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block";
|
||||
else document.getElementById("burgEditAnchorStyle").style.display = "none";
|
||||
updateMFCGFrame(burg);
|
||||
if (burg.port) byId("burgEditAnchorStyle").style.display = "inline-block";
|
||||
else byId("burgEditAnchorStyle").style.display = "none";
|
||||
updateBurgPreview(burg);
|
||||
}
|
||||
|
||||
function toggleBurgLockButton() {
|
||||
|
|
@ -374,22 +326,22 @@ function editBurg(id) {
|
|||
const id = +elSelected.attr("data-id");
|
||||
const b = pack.burgs[id];
|
||||
if (b.lock) {
|
||||
document.getElementById("burgLock").classList.remove("icon-lock-open");
|
||||
document.getElementById("burgLock").classList.add("icon-lock");
|
||||
byId("burgLock").classList.remove("icon-lock-open");
|
||||
byId("burgLock").classList.add("icon-lock");
|
||||
} else {
|
||||
document.getElementById("burgLock").classList.remove("icon-lock");
|
||||
document.getElementById("burgLock").classList.add("icon-lock-open");
|
||||
byId("burgLock").classList.remove("icon-lock");
|
||||
byId("burgLock").classList.add("icon-lock-open");
|
||||
}
|
||||
}
|
||||
|
||||
function showStyleSection() {
|
||||
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("burgStyleSection").style.display = "inline-block";
|
||||
byId("burgStyleSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideStyleSection() {
|
||||
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("burgStyleSection").style.display = "none";
|
||||
byId("burgStyleSection").style.display = "none";
|
||||
}
|
||||
|
||||
function editGroupLabelStyle() {
|
||||
|
|
@ -407,38 +359,38 @@ function editBurg(id) {
|
|||
editStyle("anchors", g);
|
||||
}
|
||||
|
||||
function updateMFCGFrame(burg) {
|
||||
const mfcgURL = getMFCGlink(burg);
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL + "&preview=1");
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
function updateBurgPreview(burg) {
|
||||
const src = getBurgLink(burg) + "&preview=1";
|
||||
|
||||
// recreate object to force reload (Chrome bug)
|
||||
const container = byId("burgPreviewObject");
|
||||
container.innerHTML = "";
|
||||
const object = document.createElement("object");
|
||||
object.style.width = "100%";
|
||||
object.data = src;
|
||||
container.insertBefore(object, null);
|
||||
}
|
||||
|
||||
function changeSeed() {
|
||||
function openBurgLink() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = +this.value;
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
|
||||
openURL(getBurgLink(burg));
|
||||
}
|
||||
|
||||
function randomizeSeed() {
|
||||
function changeBurgLink() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = rand(1e9 - 1);
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
document.getElementById("mfcgBurgSeed").value = burgSeed;
|
||||
}
|
||||
|
||||
function addCustomMfcgLink() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const message = "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed";
|
||||
prompt(message, {default: burg.link || "", required: false}, link => {
|
||||
if (link) burg.link = link;
|
||||
else delete burg.link;
|
||||
updateMFCGFrame(burg);
|
||||
});
|
||||
prompt(
|
||||
"Provide custom link to the burg map. It can be a link to Medieval Fantasy City Generator, a different tool, or just an image. Leave empty to use the default map",
|
||||
{default: getBurgLink(burg), required: false},
|
||||
link => {
|
||||
if (link) burg.link = link;
|
||||
else delete burg.link;
|
||||
updateBurgPreview(burg);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function openEmblemEdit() {
|
||||
|
|
@ -447,16 +399,16 @@ function editBurg(id) {
|
|||
editEmblem("burg", "burgCOA" + id, burg);
|
||||
}
|
||||
|
||||
function toggleMFCGMap() {
|
||||
options.showMFCGMap = !options.showMFCGMap;
|
||||
document.getElementById("mfcgPreviewSection").style.display = options.showMFCGMap ? "block" : "none";
|
||||
document.getElementById("burgToggleMFCGMap").className = options.showMFCGMap ? "icon-map" : "icon-map-o";
|
||||
function toggleBurgPreview() {
|
||||
options.showBurgPreview = !options.showBurgPreview;
|
||||
byId("burgPreviewSection").style.display = options.showBurgPreview ? "block" : "none";
|
||||
byId("burgTogglePreview").className = options.showBurgPreview ? "icon-map" : "icon-map-o";
|
||||
}
|
||||
|
||||
function toggleRelocateBurg() {
|
||||
const toggler = document.getElementById("toggleCells");
|
||||
document.getElementById("burgRelocate").classList.toggle("pressed");
|
||||
if (document.getElementById("burgRelocate").classList.contains("pressed")) {
|
||||
const toggler = byId("toggleCells");
|
||||
byId("burgRelocate").classList.toggle("pressed");
|
||||
if (byId("burgRelocate").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", relocateBurgOnClick);
|
||||
tip("Click on map to relocate burg. Hold Shift for continuous move", true);
|
||||
if (!layerIsOn("toggleCells")) {
|
||||
|
|
@ -576,8 +528,53 @@ function editBurg(id) {
|
|||
}
|
||||
|
||||
function closeBurgEditor() {
|
||||
document.getElementById("burgRelocate").classList.remove("pressed");
|
||||
byId("burgRelocate").classList.remove("pressed");
|
||||
burgLabels.selectAll("text").call(d3.drag().on("drag", null)).classed("draggable", false);
|
||||
unselect();
|
||||
}
|
||||
}
|
||||
|
||||
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
|
||||
function getTemperatureLikeness(temperature) {
|
||||
if (temperature < -5) return "Yakutsk";
|
||||
const cities = [
|
||||
"Snag (Yukon)",
|
||||
"Yellowknife (Canada)",
|
||||
"Okhotsk (Russia)",
|
||||
"Fairbanks (Alaska)",
|
||||
"Nuuk (Greenland)",
|
||||
"Murmansk", // -5 - 0
|
||||
"Arkhangelsk",
|
||||
"Anchorage",
|
||||
"Tromsø",
|
||||
"Reykjavik",
|
||||
"Riga",
|
||||
"Stockholm",
|
||||
"Halifax",
|
||||
"Prague",
|
||||
"Copenhagen",
|
||||
"London", // 1 - 10
|
||||
"Antwerp",
|
||||
"Paris",
|
||||
"Milan",
|
||||
"Batumi",
|
||||
"Rome",
|
||||
"Dubrovnik",
|
||||
"Lisbon",
|
||||
"Barcelona",
|
||||
"Marrakesh",
|
||||
"Alexandria", // 11 - 20
|
||||
"Tegucigalpa",
|
||||
"Guangzhou",
|
||||
"Rio de Janeiro",
|
||||
"Dakar",
|
||||
"Miami",
|
||||
"Jakarta",
|
||||
"Mogadishu",
|
||||
"Bangkok",
|
||||
"Aden",
|
||||
"Khartoum"
|
||||
]; // 21 - 30
|
||||
if (temperature > 30) return "Mecca";
|
||||
return cities[temperature + 5] || null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"use strict";
|
||||
function overviewBurgs() {
|
||||
function overviewBurgs(settings = {stateId: null, cultureId: null}) {
|
||||
if (customization) return;
|
||||
closeDialogs("#burgsOverview, .stable");
|
||||
if (!layerIsOn("toggleIcons")) toggleIcons();
|
||||
if (!layerIsOn("toggleLabels")) toggleLabels();
|
||||
|
||||
const body = document.getElementById("burgsBody");
|
||||
const body = byId("burgsBody");
|
||||
updateFilter();
|
||||
updateLockAllIcon();
|
||||
burgsOverviewAddLines();
|
||||
|
|
@ -23,20 +23,20 @@ function overviewBurgs() {
|
|||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("burgsOverviewRefresh").addEventListener("click", refreshBurgsEditor);
|
||||
document.getElementById("burgsChart").addEventListener("click", showBurgsChart);
|
||||
document.getElementById("burgsFilterState").addEventListener("change", burgsOverviewAddLines);
|
||||
document.getElementById("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines);
|
||||
document.getElementById("regenerateBurgNames").addEventListener("click", regenerateNames);
|
||||
document.getElementById("addNewBurg").addEventListener("click", enterAddBurgMode);
|
||||
document.getElementById("burgsExport").addEventListener("click", downloadBurgsData);
|
||||
document.getElementById("burgNamesImport").addEventListener("click", renameBurgsInBulk);
|
||||
document.getElementById("burgsListToLoad").addEventListener("change", function () {
|
||||
byId("burgsOverviewRefresh").addEventListener("click", refreshBurgsEditor);
|
||||
byId("burgsChart").addEventListener("click", showBurgsChart);
|
||||
byId("burgsFilterState").addEventListener("change", burgsOverviewAddLines);
|
||||
byId("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines);
|
||||
byId("regenerateBurgNames").addEventListener("click", regenerateNames);
|
||||
byId("addNewBurg").addEventListener("click", enterAddBurgMode);
|
||||
byId("burgsExport").addEventListener("click", downloadBurgsData);
|
||||
byId("burgNamesImport").addEventListener("click", renameBurgsInBulk);
|
||||
byId("burgsListToLoad").addEventListener("change", function () {
|
||||
uploadFile(this, importBurgNames);
|
||||
});
|
||||
document.getElementById("burgsLockAll").addEventListener("click", toggleLockAll);
|
||||
document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove);
|
||||
document.getElementById("burgsInvertLock").addEventListener("click", invertLock);
|
||||
byId("burgsLockAll").addEventListener("click", toggleLockAll);
|
||||
byId("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove);
|
||||
byId("burgsInvertLock").addEventListener("click", invertLock);
|
||||
|
||||
function refreshBurgsEditor() {
|
||||
updateFilter();
|
||||
|
|
@ -44,34 +44,34 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
function updateFilter() {
|
||||
const stateFilter = document.getElementById("burgsFilterState");
|
||||
const selectedState = stateFilter.value || 1;
|
||||
const stateFilter = byId("burgsFilterState");
|
||||
const selectedState = settings.stateId !== null ? settings.stateId : stateFilter.value || -1;
|
||||
stateFilter.options.length = 0; // remove all options
|
||||
stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1));
|
||||
stateFilter.options.add(new Option(pack.states[0].name, 0, false, !selectedState));
|
||||
stateFilter.options.add(new Option("all", -1, false, selectedState === -1));
|
||||
stateFilter.options.add(new Option(pack.states[0].name, 0, false, selectedState === 0));
|
||||
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
|
||||
|
||||
const cultureFilter = document.getElementById("burgsFilterCulture");
|
||||
const selectedCulture = cultureFilter.value || -1;
|
||||
const cultureFilter = byId("burgsFilterCulture");
|
||||
const selectedCulture = settings.cultureId !== null ? settings.cultureId : cultureFilter.value || -1;
|
||||
cultureFilter.options.length = 0; // remove all options
|
||||
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture == -1));
|
||||
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, !selectedCulture));
|
||||
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture === -1));
|
||||
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, selectedCulture === 0));
|
||||
const culturesSorted = pack.cultures.filter(c => c.i && !c.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
culturesSorted.forEach(c => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture)));
|
||||
}
|
||||
|
||||
// add line for each burg
|
||||
function burgsOverviewAddLines() {
|
||||
const selectedState = +document.getElementById("burgsFilterState").value;
|
||||
const selectedCulture = +document.getElementById("burgsFilterCulture").value;
|
||||
const selectedStateId = +byId("burgsFilterState").value;
|
||||
const selectedCultureId = +byId("burgsFilterCulture").value;
|
||||
let filtered = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
if (selectedState != -1) filtered = filtered.filter(b => b.state === selectedState); // filtered by state
|
||||
if (selectedCulture != -1) filtered = filtered.filter(b => b.culture === selectedCulture); // filtered by culture
|
||||
if (selectedStateId !== -1) filtered = filtered.filter(b => b.state === selectedStateId); // filtered by state
|
||||
if (selectedCultureId !== -1) filtered = filtered.filter(b => b.culture === selectedCultureId); // filtered by culture
|
||||
|
||||
body.innerHTML = "";
|
||||
let lines = "",
|
||||
totalPopulation = 0;
|
||||
let lines = "";
|
||||
let totalPopulation = 0;
|
||||
|
||||
for (const b of filtered) {
|
||||
const population = b.population * populationRate * urbanization;
|
||||
|
|
@ -119,6 +119,7 @@ function overviewBurgs() {
|
|||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
if (!filtered.length) body.innerHTML = /* html */ `<div style="padding-block: 0.3em;">No burgs found</div>`;
|
||||
body.insertAdjacentHTML("beforeend", lines);
|
||||
|
||||
// update footer
|
||||
|
|
@ -362,7 +363,7 @@ function overviewBurgs() {
|
|||
.attr("height", height - 10)
|
||||
.attr("stroke-width", 2);
|
||||
const graph = svg.append("g").attr("transform", `translate(-50, -10)`);
|
||||
document.getElementById("burgsTreeType").addEventListener("change", updateChart);
|
||||
byId("burgsTreeType").addEventListener("change", updateChart);
|
||||
|
||||
treeLayout(root);
|
||||
|
||||
|
|
@ -392,7 +393,7 @@ function overviewBurgs() {
|
|||
|
||||
function hideInfo(ev) {
|
||||
burgHighlightOff(ev);
|
||||
if (!document.getElementById("burgsInfo")) return;
|
||||
if (!byId("burgsInfo")) return;
|
||||
burgsInfo.innerHTML = "‍";
|
||||
d3.select(ev.target).transition().attr("stroke", null);
|
||||
tip("");
|
||||
|
|
@ -479,10 +480,7 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
function downloadBurgsData() {
|
||||
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";
|
||||
|
||||
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Temperature,Temperature likeness,Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town,Emblem,City Generator Link\n`; // headers
|
||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
|
||||
valid.forEach(b => {
|
||||
|
|
@ -503,6 +501,9 @@ function overviewBurgs() {
|
|||
data += getLatitude(b.y, 2) + ",";
|
||||
data += getLongitude(b.x, 2) + ",";
|
||||
data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
|
||||
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
|
||||
data += convertTemperature(temperature) + ",";
|
||||
data += getTemperatureLikeness(temperature) + ",";
|
||||
|
||||
// add status data
|
||||
data += b.capital ? "capital," : ",";
|
||||
|
|
@ -512,7 +513,9 @@ function overviewBurgs() {
|
|||
data += b.plaza ? "plaza," : ",";
|
||||
data += b.temple ? "temple," : ",";
|
||||
data += b.shanty ? "shanty town," : ",";
|
||||
if (options.showMFCGMap) data += getMFCGlink(b);
|
||||
data += b.coa ? JSON.stringify(b.coa).replace(/"/g, "").replace(/,/g, ";") + "," : ",";
|
||||
data += getBurgLink(b);
|
||||
|
||||
data += "\n";
|
||||
});
|
||||
|
||||
|
|
@ -614,11 +617,11 @@ function overviewBurgs() {
|
|||
});
|
||||
|
||||
burgsOverviewAddLines();
|
||||
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock" : "icon-lock-open";
|
||||
byId("burgsLockAll").className = allLocked ? "icon-lock" : "icon-lock-open";
|
||||
}
|
||||
|
||||
function updateLockAllIcon() {
|
||||
const allLocked = pack.burgs.every(({lock, i, removed}) => lock || !i || removed);
|
||||
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock-open" : "icon-lock";
|
||||
byId("burgsLockAll").className = allLocked ? "icon-lock-open" : "icon-lock";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,25 +243,22 @@ function removeBurg(id) {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleCapital(burg) {
|
||||
const state = pack.burgs[burg].state;
|
||||
if (!state) {
|
||||
tip("Neutral lands cannot have a capital", false, "error");
|
||||
return;
|
||||
}
|
||||
if (pack.burgs[burg].capital) {
|
||||
tip("To change capital please assign a capital status to another burg of this state", false, "error");
|
||||
return;
|
||||
}
|
||||
const old = pack.states[state].capital;
|
||||
function toggleCapital(burgId) {
|
||||
const {burgs, states} = pack;
|
||||
if (burgs[burgId].capital)
|
||||
return tip("To change capital please assign a capital status to another burg of this state", false, "error");
|
||||
|
||||
// change statuses
|
||||
pack.states[state].capital = burg;
|
||||
pack.states[state].center = pack.burgs[burg].cell;
|
||||
pack.burgs[burg].capital = 1;
|
||||
pack.burgs[old].capital = 0;
|
||||
moveBurgToGroup(burg, "cities");
|
||||
moveBurgToGroup(old, "towns");
|
||||
const stateId = burgs[burgId].state;
|
||||
if (!stateId) return tip("Neutral lands cannot have a capital", false, "error");
|
||||
|
||||
const prevCapitalId = states[stateId].capital;
|
||||
states[stateId].capital = burgId;
|
||||
states[stateId].center = burgs[burgId].cell;
|
||||
burgs[burgId].capital = 1;
|
||||
burgs[prevCapitalId].capital = 0;
|
||||
|
||||
moveBurgToGroup(burgId, "cities");
|
||||
moveBurgToGroup(prevCapitalId, "towns");
|
||||
}
|
||||
|
||||
function togglePort(burg) {
|
||||
|
|
@ -291,16 +288,20 @@ function togglePort(burg) {
|
|||
.attr("height", size);
|
||||
}
|
||||
|
||||
function getBurgSeed(burg) {
|
||||
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
|
||||
}
|
||||
|
||||
function getMFCGlink(burg) {
|
||||
function getBurgLink(burg) {
|
||||
if (burg.link) return burg.link;
|
||||
|
||||
const population = burg.population * populationRate * urbanization;
|
||||
if (population >= options.villageMaxPopulation || burg.citadel || burg.walls || burg.temple || burg.shanty)
|
||||
return createMfcgLink(burg);
|
||||
|
||||
return createVillageGeneratorLink(burg);
|
||||
}
|
||||
|
||||
function createMfcgLink(burg) {
|
||||
const {cells} = pack;
|
||||
const {i, name, population: burgPopulation, cell} = burg;
|
||||
const seed = getBurgSeed(burg);
|
||||
const burgSeed = burg.MFCG || seed + String(burg.i).padStart(4, 0);
|
||||
|
||||
const sizeRaw = 2.13 * Math.pow((burgPopulation * populationRate) / urbanDensity, 0.385);
|
||||
const size = minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
|
|
@ -308,11 +309,19 @@ function getMFCGlink(burg) {
|
|||
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
const coast = Number(burg.port > 0);
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : null;
|
||||
const sea = (() => {
|
||||
if (!coast || !cells.haven[cell]) return null;
|
||||
|
||||
// calculate see direction: 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
const p1 = cells.p[cell];
|
||||
const p2 = cells.p[cells.haven[cell]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
return rn(normalize(deg, 0, 360) * 2, 2);
|
||||
})();
|
||||
|
||||
const biome = cells.biome[cell];
|
||||
const arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
|
||||
const farms = +arableBiomes.includes(biome);
|
||||
const farms = +arableBiomes.includes(cells.biome[cell]);
|
||||
|
||||
const citadel = +burg.citadel;
|
||||
const urban_castle = +(citadel && each(2)(i));
|
||||
|
|
@ -324,19 +333,12 @@ function getMFCGlink(burg) {
|
|||
const temple = +burg.temple;
|
||||
const shantytown = +burg.shanty;
|
||||
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
return rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
}
|
||||
|
||||
const parameters = {
|
||||
const url = new URL("https://watabou.github.io/city-generator/");
|
||||
url.search = new URLSearchParams({
|
||||
name,
|
||||
population,
|
||||
size,
|
||||
seed,
|
||||
seed: burgSeed,
|
||||
river,
|
||||
coast,
|
||||
farms,
|
||||
|
|
@ -348,14 +350,60 @@ function getMFCGlink(burg) {
|
|||
walls,
|
||||
shantytown,
|
||||
gates: -1
|
||||
};
|
||||
const url = new URL("https://watabou.github.io/city-generator/");
|
||||
url.search = new URLSearchParams(parameters);
|
||||
});
|
||||
if (sea) url.searchParams.append("sea", sea);
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function createVillageGeneratorLink(burg) {
|
||||
const {cells, features} = pack;
|
||||
const {i, population, cell} = burg;
|
||||
|
||||
const pop = rn(population * populationRate * urbanization);
|
||||
const burgSeed = seed + String(i).padStart(4, 0);
|
||||
const tags = [];
|
||||
|
||||
if (cells.r[cell] && cells.haven[cell]) tags.push("estuary");
|
||||
else if (cells.haven[cell] && features[cells.f[cell]].cells === 1) tags.push("island,district");
|
||||
else if (burg.port) tags.push("coast");
|
||||
else if (cells.conf[cell]) tags.push("confluence");
|
||||
else if (cells.r[cell]) tags.push("river");
|
||||
else if (pop < 200 && each(4)(cell)) tags.push("pond");
|
||||
|
||||
const roadsAround = cells.c[cell].filter(c => cells.h[c] >= 20 && cells.road[c]).length;
|
||||
if (roadsAround > 1) tags.push("highway");
|
||||
else if (roadsAround === 1) tags.push("dead end");
|
||||
else tags.push("isolated");
|
||||
|
||||
const biome = cells.biome[cell];
|
||||
const arableBiomes = cells.r[cell] ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
|
||||
if (!arableBiomes.includes(biome)) tags.push("uncultivated");
|
||||
else if (each(6)(cell)) tags.push("farmland");
|
||||
|
||||
const temp = grid.cells.temp[cells.g[cell]];
|
||||
if (temp <= 0 || temp > 28 || (temp > 25 && each(3)(cell))) tags.push("no orchards");
|
||||
|
||||
if (!burg.plaza) tags.push("no square");
|
||||
|
||||
if (pop < 100) tags.push("sparse");
|
||||
else if (pop > 300) tags.push("dense");
|
||||
|
||||
const width = (() => {
|
||||
if (pop > 1500) return 1600;
|
||||
if (pop > 1000) return 1400;
|
||||
if (pop > 500) return 1000;
|
||||
if (pop > 200) return 800;
|
||||
if (pop > 100) return 600;
|
||||
return 400;
|
||||
})();
|
||||
const height = rn(width / 2.2);
|
||||
|
||||
const url = new URL("https://watabou.github.io/village-generator/");
|
||||
url.search = new URLSearchParams({pop, name: "", seed: burgSeed, width, height, tags});
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
// draw legend box
|
||||
function drawLegend(name, data) {
|
||||
legend.selectAll("*").remove(); // fully redraw every time
|
||||
|
|
@ -1096,12 +1144,12 @@ function selectIcon(initial, callback) {
|
|||
input.oninput = e => callback(input.value);
|
||||
table.onclick = e => {
|
||||
if (e.target.tagName === "TD") {
|
||||
input.value = e.target.innerHTML;
|
||||
input.value = e.target.textContent;
|
||||
callback(input.value);
|
||||
}
|
||||
};
|
||||
table.onmouseover = e => {
|
||||
if (e.target.tagName === "TD") tip(`Click to select ${e.target.innerHTML} icon`);
|
||||
if (e.target.tagName === "TD") tip(`Click to select ${e.target.textContent} icon`);
|
||||
};
|
||||
|
||||
$("#iconSelector").dialog({
|
||||
|
|
@ -1176,18 +1224,18 @@ function refreshAllEditors() {
|
|||
// dynamically loaded editors
|
||||
async function editStates() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/states-editor.js?v=1.92.00");
|
||||
const Editor = await import("../dynamic/editors/states-editor.js?v=1.96.06");
|
||||
Editor.open();
|
||||
}
|
||||
|
||||
async function editCultures() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.91.00");
|
||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.96.01");
|
||||
Editor.open();
|
||||
}
|
||||
|
||||
async function editReligions() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.89.10");
|
||||
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.96.00");
|
||||
Editor.open();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,8 +193,15 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
.attr("d", "M0,0 V4 L2,2 Z")
|
||||
.attr("fill", "darkgray");
|
||||
|
||||
let colors = getColorScheme(terrs.attr("scheme"));
|
||||
const landdef = chart.select("defs").append("linearGradient").attr("id", "landdef").attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
||||
const colors = getColorScheme("natural");
|
||||
const landdef = chart
|
||||
.select("defs")
|
||||
.append("linearGradient")
|
||||
.attr("id", "landdef")
|
||||
.attr("x1", "0%")
|
||||
.attr("y1", "0%")
|
||||
.attr("x2", "0%")
|
||||
.attr("y2", "100%");
|
||||
|
||||
if (chartData.mah == chartData.mih) {
|
||||
landdef
|
||||
|
|
@ -247,7 +254,14 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += "Z";
|
||||
chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)");
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epland")
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("stroke", "purple")
|
||||
.attr("stroke-width", "0")
|
||||
.attr("fill", "url(#landdef)");
|
||||
|
||||
// biome / heights
|
||||
let g = chart.append("g").attr("id", "epbiomes");
|
||||
|
|
@ -289,7 +303,14 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
chartData.cell[k] +
|
||||
")";
|
||||
|
||||
g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip);
|
||||
g.append("rect")
|
||||
.attr("stroke", c)
|
||||
.attr("fill", c)
|
||||
.attr("x", x)
|
||||
.attr("y", y)
|
||||
.attr("width", xscale(1))
|
||||
.attr("height", 15)
|
||||
.attr("data-tip", dataTip);
|
||||
}
|
||||
|
||||
const xAxis = d3
|
||||
|
|
@ -371,7 +392,17 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
// arrow from burg name to graph line
|
||||
g.append("path")
|
||||
.attr("id", "eparrow" + b)
|
||||
.attr("d", "M" + x1.toString() + "," + (y1 + 3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1] - 3).toString())
|
||||
.attr(
|
||||
"d",
|
||||
"M" +
|
||||
x1.toString() +
|
||||
"," +
|
||||
(y1 + 3).toString() +
|
||||
"L" +
|
||||
x1.toString() +
|
||||
"," +
|
||||
parseInt(chartData.points[k][1] - 3).toString()
|
||||
)
|
||||
.attr("stroke", "darkgray")
|
||||
.attr("fill", "lightgray")
|
||||
.attr("stroke-width", "1")
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
"use strict";
|
||||
// Module to store general UI functions
|
||||
// Module to store generic UI functions
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
window.addEventListener("resize", function (e) {
|
||||
if (stored("mapWidth") && stored("mapHeight")) return;
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
changeMapSize();
|
||||
fitMapToScreen();
|
||||
});
|
||||
|
||||
if (location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1") {
|
||||
if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1") {
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +27,7 @@ const tipBackgroundMap = {
|
|||
error: "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"
|
||||
};
|
||||
|
||||
function tip(tip = "Tip is undefined", main = false, type = "info", time = 0) {
|
||||
function tip(tip, main = false, type = "info", time = 0) {
|
||||
tooltip.innerHTML = tip;
|
||||
tooltip.style.background = tipBackgroundMap[type];
|
||||
|
||||
|
|
@ -536,7 +535,9 @@ function showInfo() {
|
|||
<li>${Armoria}: a tool for creating heraldic coats of arms</li>
|
||||
<li>${Deorum}: a vast gallery of customizable fantasy characters</li>
|
||||
</ul>
|
||||
</p>`;
|
||||
</p>
|
||||
|
||||
<p>Chinese localization: <a href="https://www.8desk.top" target="_blank">8desk.top</a></p>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ function handleKeyup(event) {
|
|||
else if (code === "KeyK") toggleMarkers();
|
||||
else if (code === "Equal" && !customization) toggleRulers();
|
||||
else if (code === "Slash") toggleScaleBar();
|
||||
else if (code === "BracketLeft") toggleVignette();
|
||||
else if (code === "ArrowLeft") zoom.translateBy(svg, 10, 0);
|
||||
else if (code === "ArrowRight") zoom.translateBy(svg, -10, 0);
|
||||
else if (code === "ArrowUp") zoom.translateBy(svg, 0, 10);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ function getDefaultPresets() {
|
|||
"toggleRivers",
|
||||
"toggleRoutes",
|
||||
"toggleScaleBar",
|
||||
"toggleStates"
|
||||
"toggleStates",
|
||||
"toggleVignette"
|
||||
],
|
||||
cultural: [
|
||||
"toggleBorders",
|
||||
|
|
@ -23,7 +24,8 @@ function getDefaultPresets() {
|
|||
"toggleLabels",
|
||||
"toggleRivers",
|
||||
"toggleRoutes",
|
||||
"toggleScaleBar"
|
||||
"toggleScaleBar",
|
||||
"toggleVignette"
|
||||
],
|
||||
religions: [
|
||||
"toggleBorders",
|
||||
|
|
@ -32,12 +34,13 @@ function getDefaultPresets() {
|
|||
"toggleReligions",
|
||||
"toggleRivers",
|
||||
"toggleRoutes",
|
||||
"toggleScaleBar"
|
||||
"toggleScaleBar",
|
||||
"toggleVignette"
|
||||
],
|
||||
provinces: ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"],
|
||||
biomes: ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar"],
|
||||
heightmap: ["toggleHeight", "toggleRivers"],
|
||||
physical: ["toggleCoordinates", "toggleHeight", "toggleIce", "toggleRivers", "toggleScaleBar"],
|
||||
provinces: ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar", "toggleVignette"],
|
||||
biomes: ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar", "toggleVignette"],
|
||||
heightmap: ["toggleHeight", "toggleRivers", "toggleVignette"],
|
||||
physical: ["toggleCoordinates", "toggleHeight", "toggleIce", "toggleRivers", "toggleScaleBar", "toggleVignette"],
|
||||
poi: [
|
||||
"toggleBorders",
|
||||
"toggleHeight",
|
||||
|
|
@ -46,7 +49,8 @@ function getDefaultPresets() {
|
|||
"toggleMarkers",
|
||||
"toggleRivers",
|
||||
"toggleRoutes",
|
||||
"toggleScaleBar"
|
||||
"toggleScaleBar",
|
||||
"toggleVignette"
|
||||
],
|
||||
military: [
|
||||
"toggleBorders",
|
||||
|
|
@ -56,7 +60,8 @@ function getDefaultPresets() {
|
|||
"toggleRivers",
|
||||
"toggleRoutes",
|
||||
"toggleScaleBar",
|
||||
"toggleStates"
|
||||
"toggleStates",
|
||||
"toggleVignette"
|
||||
],
|
||||
emblems: [
|
||||
"toggleBorders",
|
||||
|
|
@ -66,7 +71,8 @@ function getDefaultPresets() {
|
|||
"toggleRivers",
|
||||
"toggleRoutes",
|
||||
"toggleScaleBar",
|
||||
"toggleStates"
|
||||
"toggleStates",
|
||||
"toggleVignette"
|
||||
],
|
||||
landmass: ["toggleScaleBar"]
|
||||
};
|
||||
|
|
@ -157,6 +163,7 @@ function getCurrentPreset() {
|
|||
|
||||
// run on map regeneration
|
||||
function restoreLayers() {
|
||||
if (layerIsOn("toggleTexture")) drawTexture();
|
||||
if (layerIsOn("toggleHeight")) drawHeightmap();
|
||||
if (layerIsOn("toggleCells")) drawCells();
|
||||
if (layerIsOn("toggleGrid")) drawGrid();
|
||||
|
|
@ -181,92 +188,135 @@ function restoreLayers() {
|
|||
}
|
||||
|
||||
function toggleHeight(event) {
|
||||
if (customization === 1) {
|
||||
tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
|
||||
return;
|
||||
}
|
||||
if (customization === 1) return tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
|
||||
|
||||
if (!terrs.selectAll("*").size()) {
|
||||
const children = terrs.selectAll("#oceanHeights > *, #landHeights > *");
|
||||
if (!children.size()) {
|
||||
turnButtonOn("toggleHeight");
|
||||
drawHeightmap();
|
||||
if (event && isCtrlClick(event)) editStyle("terrs");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("terrs");
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle("terrs");
|
||||
turnButtonOff("toggleHeight");
|
||||
terrs.selectAll("*").remove();
|
||||
children.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function drawHeightmap() {
|
||||
TIME && console.time("drawHeightmap");
|
||||
terrs.selectAll("*").remove();
|
||||
|
||||
const {cells, vertices} = pack;
|
||||
const n = cells.i.length;
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
const paths = new Array(101).fill("");
|
||||
const ocean = terrs.select("#oceanHeights");
|
||||
const land = terrs.select("#landHeights");
|
||||
|
||||
const scheme = getColorScheme(terrs.attr("scheme"));
|
||||
const terracing = terrs.attr("terracing") / 10; // add additional shifted darker layer for pseudo-3d effect
|
||||
const skip = +terrs.attr("skip") + 1;
|
||||
const simplification = +terrs.attr("relax");
|
||||
ocean.selectAll("*").remove();
|
||||
land.selectAll("*").remove();
|
||||
|
||||
switch (+terrs.attr("curve")) {
|
||||
case 0:
|
||||
lineGen.curve(d3.curveBasisClosed);
|
||||
break;
|
||||
case 1:
|
||||
lineGen.curve(d3.curveLinear);
|
||||
break;
|
||||
case 2:
|
||||
lineGen.curve(d3.curveStep);
|
||||
break;
|
||||
default:
|
||||
lineGen.curve(d3.curveBasisClosed);
|
||||
const paths = new Array(101);
|
||||
|
||||
// ocean cells
|
||||
const renderOceanCells = Boolean(+ocean.attr("data-render"));
|
||||
if (renderOceanCells) {
|
||||
const {cells, vertices} = grid;
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
|
||||
const skip = +ocean.attr("skip") + 1 || 1;
|
||||
const relax = +ocean.attr("relax") || 0;
|
||||
lineGen.curve(d3[ocean.attr("curve") || "curveBasisClosed"]);
|
||||
|
||||
let currentLayer = 0;
|
||||
const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]);
|
||||
|
||||
for (const i of heights) {
|
||||
const h = cells.h[i];
|
||||
if (h > currentLayer) currentLayer += skip;
|
||||
if (h < currentLayer) continue;
|
||||
if (currentLayer >= 20) break;
|
||||
if (used[i]) continue; // already marked
|
||||
const onborder = cells.c[i].some(n => cells.h[n] < h);
|
||||
if (!onborder) continue;
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h));
|
||||
const chain = connectVertices(cells, vertices, vertex, h, used);
|
||||
if (chain.length < 3) continue;
|
||||
const points = simplifyLine(chain, relax).map(v => vertices.p[v]);
|
||||
if (!paths[h]) paths[h] = "";
|
||||
paths[h] += round(lineGen(points));
|
||||
}
|
||||
}
|
||||
|
||||
let currentLayer = 20;
|
||||
const heights = cells.i.sort((a, b) => cells.h[a] - cells.h[b]);
|
||||
for (const i of heights) {
|
||||
const h = cells.h[i];
|
||||
if (h > currentLayer) currentLayer += skip;
|
||||
if (currentLayer > 100) break; // no layers possible with height > 100
|
||||
if (h < currentLayer) continue;
|
||||
if (used[i]) continue; // already marked
|
||||
const onborder = cells.c[i].some(n => cells.h[n] < h);
|
||||
if (!onborder) continue;
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h));
|
||||
const chain = connectVertices(vertex, h);
|
||||
if (chain.length < 3) continue;
|
||||
const points = simplifyLine(chain).map(v => vertices.p[v]);
|
||||
paths[h] += round(lineGen(points));
|
||||
// land cells
|
||||
{
|
||||
const {cells, vertices} = pack;
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
|
||||
const skip = +land.attr("skip") + 1 || 1;
|
||||
const relax = +land.attr("relax") || 0;
|
||||
lineGen.curve(d3[land.attr("curve") || "curveBasisClosed"]);
|
||||
|
||||
let currentLayer = 20;
|
||||
const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]);
|
||||
for (const i of heights) {
|
||||
const h = cells.h[i];
|
||||
if (h > currentLayer) currentLayer += skip;
|
||||
if (h < currentLayer) continue;
|
||||
if (currentLayer > 100) break; // no layers possible with height > 100
|
||||
if (used[i]) continue; // already marked
|
||||
const onborder = cells.c[i].some(n => cells.h[n] < h);
|
||||
if (!onborder) continue;
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h));
|
||||
const chain = connectVertices(cells, vertices, vertex, h, used);
|
||||
if (chain.length < 3) continue;
|
||||
const points = simplifyLine(chain, relax).map(v => vertices.p[v]);
|
||||
if (!paths[h]) paths[h] = "";
|
||||
paths[h] += round(lineGen(points));
|
||||
}
|
||||
}
|
||||
|
||||
terrs
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", graphWidth)
|
||||
.attr("height", graphHeight)
|
||||
.attr("fill", scheme(0.8)); // draw base layer
|
||||
for (const i of d3.range(20, 101)) {
|
||||
if (paths[i].length < 10) continue;
|
||||
const color = getColor(i, scheme);
|
||||
if (terracing)
|
||||
terrs
|
||||
.append("path")
|
||||
.attr("d", paths[i])
|
||||
.attr("transform", "translate(.7,1.4)")
|
||||
.attr("fill", d3.color(color).darker(terracing))
|
||||
.attr("data-height", i);
|
||||
terrs.append("path").attr("d", paths[i]).attr("fill", color).attr("data-height", i);
|
||||
// render paths
|
||||
for (const height of d3.range(0, 101)) {
|
||||
const group = height < 20 ? ocean : land;
|
||||
const scheme = getColorScheme(group.attr("scheme"));
|
||||
|
||||
if (height === 0 && renderOceanCells) {
|
||||
// draw base ocean layer
|
||||
group
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", graphWidth)
|
||||
.attr("height", graphHeight)
|
||||
.attr("fill", scheme(1));
|
||||
}
|
||||
|
||||
if (height === 20) {
|
||||
// draw base land layer
|
||||
group
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", graphWidth)
|
||||
.attr("height", graphHeight)
|
||||
.attr("fill", scheme(0.8));
|
||||
}
|
||||
|
||||
if (paths[height] && paths[height].length >= 10) {
|
||||
const terracing = group.attr("terracing") / 10 || 0;
|
||||
const color = getColor(height, scheme);
|
||||
|
||||
if (terracing) {
|
||||
group
|
||||
.append("path")
|
||||
.attr("d", paths[height])
|
||||
.attr("transform", "translate(.7,1.4)")
|
||||
.attr("fill", d3.color(color).darker(terracing))
|
||||
.attr("data-height", height);
|
||||
}
|
||||
group.append("path").attr("d", paths[height]).attr("fill", color).attr("data-height", height);
|
||||
}
|
||||
}
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(start, h) {
|
||||
function connectVertices(cells, vertices, start, h, used) {
|
||||
const n = cells.i.length;
|
||||
const chain = []; // vertices chain to form a path
|
||||
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||
const prev = chain[chain.length - 1]; // previous vertex in chain
|
||||
|
|
@ -288,7 +338,7 @@ function drawHeightmap() {
|
|||
return chain;
|
||||
}
|
||||
|
||||
function simplifyLine(chain) {
|
||||
function simplifyLine(chain, simplification) {
|
||||
if (!simplification) return chain;
|
||||
const n = simplification + 1; // filter each nth element
|
||||
return chain.filter((d, i) => i % n === 0);
|
||||
|
|
@ -297,15 +347,7 @@ function drawHeightmap() {
|
|||
TIME && console.timeEnd("drawHeightmap");
|
||||
}
|
||||
|
||||
function getColorScheme(scheme) {
|
||||
if (scheme === "bright") return d3.scaleSequential(d3.interpolateSpectral);
|
||||
if (scheme === "light") return d3.scaleSequential(d3.interpolateRdYlGn);
|
||||
if (scheme === "green") return d3.scaleSequential(d3.interpolateGreens);
|
||||
if (scheme === "monochrome") return d3.scaleSequential(d3.interpolateGreys);
|
||||
return d3.scaleSequential(d3.interpolateSpectral);
|
||||
}
|
||||
|
||||
function getColor(value, scheme = getColorScheme()) {
|
||||
function getColor(value, scheme = getColorScheme("bright")) {
|
||||
return scheme(1 - (value < 20 ? value - 5 : value) / 100);
|
||||
}
|
||||
|
||||
|
|
@ -1517,18 +1559,30 @@ function toggleRelief(event) {
|
|||
function toggleTexture(event) {
|
||||
if (!layerIsOn("toggleTexture")) {
|
||||
turnButtonOn("toggleTexture");
|
||||
// href is not set directly to avoid image loading when layer is off
|
||||
const textureImage = byId("textureImage");
|
||||
if (textureImage) textureImage.setAttribute("href", textureImage.getAttribute("src"));
|
||||
|
||||
drawTexture();
|
||||
if (event && isCtrlClick(event)) editStyle("texture");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) return editStyle("texture");
|
||||
turnButtonOff("toggleTexture");
|
||||
texture.select("image").attr("href", null);
|
||||
texture.select("image").remove();
|
||||
}
|
||||
}
|
||||
|
||||
function drawTexture() {
|
||||
const x = Number(texture.attr("data-x") || 0);
|
||||
const y = Number(texture.attr("data-y") || 0);
|
||||
const href = texture.attr("data-href");
|
||||
|
||||
texture
|
||||
.append("image")
|
||||
.attr("preserveAspectRatio", "xMidYMid slice")
|
||||
.attr("x", x)
|
||||
.attr("y", y)
|
||||
.attr("width", graphWidth - x)
|
||||
.attr("height", graphHeight - y)
|
||||
.attr("href", href);
|
||||
}
|
||||
|
||||
function toggleRivers(event) {
|
||||
if (!layerIsOn("toggleRivers")) {
|
||||
turnButtonOn("toggleRivers");
|
||||
|
|
@ -1659,10 +1713,7 @@ function toggleLabels(event) {
|
|||
invokeActiveZooming();
|
||||
if (event && isCtrlClick(event)) editStyle("labels");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("labels");
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle("labels");
|
||||
turnButtonOff("toggleLabels");
|
||||
labels.style("display", "none");
|
||||
}
|
||||
|
|
@ -1674,10 +1725,7 @@ function toggleIcons(event) {
|
|||
$("#icons").fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle("burgIcons");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("burgIcons");
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle("burgIcons");
|
||||
turnButtonOff("toggleIcons");
|
||||
$("#icons").fadeOut();
|
||||
}
|
||||
|
|
@ -1690,10 +1738,7 @@ function toggleRulers(event) {
|
|||
rulers.draw();
|
||||
ruler.style("display", null);
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("ruler");
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle("ruler");
|
||||
turnButtonOff("toggleRulers");
|
||||
ruler.selectAll("*").remove();
|
||||
ruler.style("display", "none");
|
||||
|
|
@ -1704,17 +1749,113 @@ function toggleScaleBar(event) {
|
|||
if (!layerIsOn("toggleScaleBar")) {
|
||||
turnButtonOn("toggleScaleBar");
|
||||
$("#scaleBar").fadeIn();
|
||||
if (event && isCtrlClick(event)) editUnits();
|
||||
if (event && isCtrlClick(event)) editStyle("scaleBar");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editUnits();
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle("scaleBar");
|
||||
$("#scaleBar").fadeOut();
|
||||
turnButtonOff("toggleScaleBar");
|
||||
}
|
||||
}
|
||||
|
||||
function drawScaleBar(scaleBar, scaleLevel) {
|
||||
if (!scaleBar.size() || scaleBar.style("display") === "none") return;
|
||||
|
||||
const distanceScale = +distanceScaleInput.value;
|
||||
const unit = distanceUnitInput.value;
|
||||
const size = +scaleBar.attr("data-bar-size");
|
||||
|
||||
const length = (function () {
|
||||
const init = 100;
|
||||
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
|
||||
if (val > 900) val = rn(val, -3); // round to 1000
|
||||
else if (val > 90) val = rn(val, -2); // round to 100
|
||||
else if (val > 9) val = rn(val, -1); // round to 10
|
||||
else val = rn(val); // round to 1
|
||||
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
|
||||
return length;
|
||||
})();
|
||||
|
||||
scaleBar.select("#scaleBarContent").remove(); // redraw content every time
|
||||
const content = scaleBar.append("g").attr("id", "scaleBarContent");
|
||||
|
||||
const lines = content.append("g");
|
||||
lines
|
||||
.append("line")
|
||||
.attr("x1", 0.5)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", length + size - 0.5)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "white");
|
||||
lines
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", size)
|
||||
.attr("x2", length + size)
|
||||
.attr("y2", size)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
lines
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", length + size)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", rn(size * 3, 2))
|
||||
.attr("stroke-dasharray", size + " " + rn(length / 5 - size, 2))
|
||||
.attr("stroke", "#3d3d3d");
|
||||
|
||||
const texts = content.append("g").attr("text-anchor", "middle").attr("font-family", "var(--serif)");
|
||||
texts
|
||||
.selectAll("text")
|
||||
.data(d3.range(0, 6))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", d => rn((d * length) / 5, 2))
|
||||
.attr("y", 0)
|
||||
.attr("dy", "-.6em")
|
||||
.text(d => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? "" : " " + unit));
|
||||
|
||||
const label = scaleBar.attr("data-label");
|
||||
if (label) {
|
||||
texts
|
||||
.append("text")
|
||||
.attr("x", (length + 1) / 2)
|
||||
.attr("dy", ".6em")
|
||||
.attr("dominant-baseline", "text-before-edge")
|
||||
.text(label);
|
||||
}
|
||||
|
||||
const scaleBarBack = scaleBar.select("#scaleBarBack");
|
||||
if (scaleBarBack.size()) {
|
||||
const bbox = content.node().getBBox();
|
||||
const paddingTop = +scaleBarBack.attr("data-top") || 0;
|
||||
const paddingLeft = +scaleBarBack.attr("data-left") || 0;
|
||||
const paddingRight = +scaleBarBack.attr("data-right") || 0;
|
||||
const paddingBottom = +scaleBarBack.attr("data-bottom") || 0;
|
||||
|
||||
scaleBar
|
||||
.select("#scaleBarBack")
|
||||
.attr("x", -paddingLeft)
|
||||
.attr("y", -paddingTop)
|
||||
.attr("width", bbox.width + paddingRight)
|
||||
.attr("height", bbox.height + paddingBottom);
|
||||
}
|
||||
}
|
||||
|
||||
// fit ScaleBar to screen size
|
||||
function fitScaleBar(scaleBar, fullWidth, fullHeight) {
|
||||
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
|
||||
|
||||
const posX = +scaleBar.attr("data-x") || 99;
|
||||
const posY = +scaleBar.attr("data-y") || 99;
|
||||
const bbox = scaleBar.select("rect").node().getBBox();
|
||||
|
||||
const x = rn((fullWidth * posX) / 100 - bbox.width + 10);
|
||||
const y = rn((fullHeight * posY) / 100 - bbox.height + 20);
|
||||
scaleBar.attr("transform", `translate(${x},${y})`);
|
||||
}
|
||||
|
||||
function toggleZones(event) {
|
||||
if (!layerIsOn("toggleZones")) {
|
||||
turnButtonOn("toggleZones");
|
||||
|
|
@ -1737,10 +1878,7 @@ function toggleEmblems(event) {
|
|||
$("#emblems").fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle("emblems");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("emblems");
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle("emblems");
|
||||
$("#emblems").fadeOut();
|
||||
turnButtonOff("toggleEmblems");
|
||||
}
|
||||
|
|
@ -1857,6 +1995,18 @@ function drawEmblems() {
|
|||
TIME && console.timeEnd("drawEmblems");
|
||||
}
|
||||
|
||||
function toggleVignette(event) {
|
||||
if (!layerIsOn("toggleVignette")) {
|
||||
turnButtonOn("toggleVignette");
|
||||
$("#vignette").fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle("vignette");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) return editStyle("vignette");
|
||||
$("#vignette").fadeOut();
|
||||
turnButtonOff("toggleVignette");
|
||||
}
|
||||
}
|
||||
|
||||
function layerIsOn(el) {
|
||||
const buttonoff = document.getElementById(el).classList.contains("buttonoff");
|
||||
return !buttonoff;
|
||||
|
|
|
|||
|
|
@ -532,101 +532,3 @@ class Planimeter extends Measurer {
|
|||
this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area);
|
||||
}
|
||||
}
|
||||
|
||||
// Scale bar
|
||||
function drawScaleBar(scaleLevel) {
|
||||
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
|
||||
scaleBar.selectAll("*").remove(); // fully redraw every time
|
||||
|
||||
const distanceScale = +distanceScaleInput.value;
|
||||
const unit = distanceUnitInput.value;
|
||||
const size = +barSizeInput.value;
|
||||
|
||||
// calculate size
|
||||
const init = 100;
|
||||
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
|
||||
if (val > 900) val = rn(val, -3);
|
||||
// round to 1000
|
||||
else if (val > 90) val = rn(val, -2);
|
||||
// round to 100
|
||||
else if (val > 9) val = rn(val, -1);
|
||||
// round to 10
|
||||
else val = rn(val); // round to 1
|
||||
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
|
||||
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0.5)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", length + size - 0.5)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "white");
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", size)
|
||||
.attr("x2", length + size)
|
||||
.attr("y2", size)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
const dash = size + " " + rn(length / 5 - size, 2);
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", length + size)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", rn(size * 3, 2))
|
||||
.attr("stroke-dasharray", dash)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
|
||||
const fontSize = rn(5 * size, 1);
|
||||
scaleBar
|
||||
.selectAll("text")
|
||||
.data(d3.range(0, 6))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", d => rn((d * length) / 5, 2))
|
||||
.attr("y", 0)
|
||||
.attr("dy", "-.5em")
|
||||
.attr("font-size", fontSize)
|
||||
.text(d => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? "" : " " + unit));
|
||||
|
||||
if (barLabel.value !== "") {
|
||||
scaleBar
|
||||
.append("text")
|
||||
.attr("x", (length + 1) / 2)
|
||||
.attr("y", 2 * size)
|
||||
.attr("dominant-baseline", "text-before-edge")
|
||||
.attr("font-size", fontSize)
|
||||
.text(barLabel.value);
|
||||
}
|
||||
|
||||
const bbox = scaleBar.node().getBBox();
|
||||
// append backbround rectangle
|
||||
scaleBar
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", -10)
|
||||
.attr("y", -20)
|
||||
.attr("width", bbox.width + 10)
|
||||
.attr("height", bbox.height + 15)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "none")
|
||||
.attr("filter", "url(#blur5)")
|
||||
.attr("fill", barBackColor.value)
|
||||
.attr("opacity", +barBackOpacity.value);
|
||||
|
||||
fitScaleBar();
|
||||
}
|
||||
|
||||
// fit ScaleBar to canvas size
|
||||
function fitScaleBar() {
|
||||
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
|
||||
const px = isNaN(+barPosX.value) ? 0.99 : barPosX.value / 100;
|
||||
const py = isNaN(+barPosY.value) ? 0.99 : barPosY.value / 100;
|
||||
const bbox = scaleBar.select("rect").node().getBBox();
|
||||
const x = rn(svgWidth * px - bbox.width + 10),
|
||||
y = rn(svgHeight * py - bbox.height + 20);
|
||||
scaleBar.attr("transform", `translate(${x},${y})`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@ function overviewMilitary() {
|
|||
const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, " "));
|
||||
insert(`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
||||
insert(
|
||||
`<div data-tip="State ${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 () {
|
||||
|
|
@ -76,7 +78,9 @@ function overviewMilitary() {
|
|||
const rate = (total / population) * 100;
|
||||
|
||||
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
|
||||
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
|
||||
const lineData = options.military
|
||||
.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`)
|
||||
.join(" ");
|
||||
|
||||
lines += /* html */ `<div
|
||||
class="states"
|
||||
|
|
@ -91,9 +95,14 @@ function overviewMilitary() {
|
|||
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
|
||||
${lineData}
|
||||
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
|
||||
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(
|
||||
total
|
||||
)}</div>
|
||||
<div data-type="population" data-tip="State population">${si(population)}</div>
|
||||
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
|
||||
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(
|
||||
rate,
|
||||
2
|
||||
)}%</div>
|
||||
<input
|
||||
data-tip="War Alert. Editable modifier to military forces number, depends of political situation"
|
||||
style="width:4.1em"
|
||||
|
|
@ -131,7 +140,9 @@ function overviewMilitary() {
|
|||
});
|
||||
|
||||
const getForces = u => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
|
||||
options.military.forEach(u => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)));
|
||||
options.military.forEach(
|
||||
u => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u))
|
||||
);
|
||||
|
||||
const population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||
const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0));
|
||||
|
|
@ -237,7 +248,16 @@ function overviewMilitary() {
|
|||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Apply: applyMilitaryOptions,
|
||||
Add: () => addUnitLine({icon: "🛡️", name: "custom" + militaryOptionsTable.rows.length, rural: 0.2, urban: 0.5, crew: 1, power: 1, type: "melee"}),
|
||||
Add: () =>
|
||||
addUnitLine({
|
||||
icon: "🛡️",
|
||||
name: "custom" + militaryOptionsTable.rows.length,
|
||||
rural: 0.2,
|
||||
urban: 0.5,
|
||||
crew: 1,
|
||||
power: 1,
|
||||
type: "melee"
|
||||
}),
|
||||
Restore: restoreDefaultUnits,
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
|
|
@ -262,7 +282,7 @@ function overviewMilitary() {
|
|||
if (el.tagName !== "BUTTON") return;
|
||||
const type = el.dataset.type;
|
||||
|
||||
if (type === "icon") return selectIcon(el.innerHTML, v => (el.innerHTML = v));
|
||||
if (type === "icon") return selectIcon(el.textContent, v => (el.textContent = v));
|
||||
if (type === "biomes") {
|
||||
const {i, name, color} = biomesData;
|
||||
const biomesArray = Array(i.length).fill(null);
|
||||
|
|
@ -294,7 +314,9 @@ function overviewMilitary() {
|
|||
function addUnitLine(unit) {
|
||||
const {type, icon, name, rural, urban, power, crew, separate} = unit;
|
||||
const row = document.createElement("tr");
|
||||
const typeOptions = types.map(t => `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ");
|
||||
const typeOptions = types
|
||||
.map(t => `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`)
|
||||
.join(" ");
|
||||
|
||||
const getLimitButton = attr =>
|
||||
`<button
|
||||
|
|
@ -305,7 +327,9 @@ function overviewMilitary() {
|
|||
${getLimitText(unit[attr])}
|
||||
</button>`;
|
||||
|
||||
row.innerHTML = /* html */ `<td><button data-type="icon" data-tip="Click to select unit icon">${icon || " "}</button></td>
|
||||
row.innerHTML = /* html */ `<td><button data-type="icon" data-tip="Click to select unit icon">${
|
||||
icon || " "
|
||||
}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${name}" /></td>
|
||||
<td>${getLimitButton("biomes")}</td>
|
||||
<td>${getLimitButton("states")}</td>
|
||||
|
|
@ -344,7 +368,9 @@ function overviewMilitary() {
|
|||
const lines = filtered.map(
|
||||
({i, name, fullName, color}) =>
|
||||
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
|
||||
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? "checked" : ""} >
|
||||
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${
|
||||
!initial.length || initial.includes(i) ? "checked" : ""
|
||||
} >
|
||||
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
||||
</td></tr>`
|
||||
);
|
||||
|
|
@ -387,22 +413,21 @@ function overviewMilitary() {
|
|||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(tableBody.querySelectorAll("tr"));
|
||||
const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_"));
|
||||
if (new Set(names).size !== names.length) {
|
||||
tip("All units should have unique names", false, "error");
|
||||
return;
|
||||
}
|
||||
if (new Set(names).size !== names.length) return tip("All units should have unique names", false, "error");
|
||||
|
||||
$("#militaryOptions").dialog("close");
|
||||
|
||||
options.military = unitLines.map((r, i) => {
|
||||
const elements = Array.from(r.querySelectorAll("input, button, select"));
|
||||
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map(el => {
|
||||
const {type, value} = el.dataset || {};
|
||||
if (type === "icon") return el.innerHTML || "⠀";
|
||||
if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
|
||||
if (el.type === "number") return +el.value || 0;
|
||||
if (el.type === "checkbox") return +el.checked || 0;
|
||||
return el.value;
|
||||
});
|
||||
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] =
|
||||
elements.map(el => {
|
||||
const {type, value} = el.dataset || {};
|
||||
if (type === "icon") return el.textContent || "⠀";
|
||||
if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
|
||||
if (el.type === "number") return +el.value || 0;
|
||||
if (el.type === "checkbox") return +el.checked || 0;
|
||||
return el.value;
|
||||
});
|
||||
|
||||
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
if (biomes) unit.biomes = biomes;
|
||||
|
|
@ -419,7 +444,8 @@ function overviewMilitary() {
|
|||
}
|
||||
|
||||
function militaryRecalculate() {
|
||||
alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated";
|
||||
alertMessage.innerHTML =
|
||||
"Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove regiment",
|
||||
|
|
|
|||
|
|
@ -244,11 +244,13 @@ function editNamesbase() {
|
|||
|
||||
Names.clearChains();
|
||||
if (override) nameBases = [];
|
||||
const unsafe = new RegExp(/[|/]/, "g");
|
||||
|
||||
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});
|
||||
const [rawName, min, max, d, m, rawNames] = base.split("|");
|
||||
const name = rawName.replace(unsafe, "");
|
||||
const names = rawNames.replace(unsafe, "");
|
||||
nameBases.push({name, min, max, d, m, b: names});
|
||||
});
|
||||
|
||||
createBasesList();
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@
|
|||
|
||||
function editNotes(id, name) {
|
||||
// elements
|
||||
const notesLegend = document.getElementById("notesLegend");
|
||||
const notesName = document.getElementById("notesName");
|
||||
const notesSelect = document.getElementById("notesSelect");
|
||||
const notesPin = document.getElementById("notesPin");
|
||||
const notesLegend = byId("notesLegend");
|
||||
const notesName = byId("notesName");
|
||||
const notesSelect = byId("notesSelect");
|
||||
const notesPin = byId("notesPin");
|
||||
|
||||
// update list of objects
|
||||
notesSelect.options.length = 0;
|
||||
for (const note of notes) {
|
||||
notesSelect.options.add(new Option(note.id, note.id));
|
||||
}
|
||||
notes.forEach(({id}) => notesSelect.options.add(new Option(id, id)));
|
||||
|
||||
// update pin notes icon
|
||||
const notesArePinned = options.pinNotes;
|
||||
|
|
@ -22,7 +20,7 @@ function editNotes(id, name) {
|
|||
if (notes.length || id) {
|
||||
if (!id) id = notes[0].id;
|
||||
let note = notes.find(note => note.id === id);
|
||||
if (note === undefined) {
|
||||
if (!note) {
|
||||
if (!name) name = id;
|
||||
note = {id, name, legend: ""};
|
||||
notes.push(note);
|
||||
|
|
@ -52,17 +50,17 @@ function editNotes(id, name) {
|
|||
modules.editNotes = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("notesSelect").addEventListener("change", changeElement);
|
||||
document.getElementById("notesName").addEventListener("input", changeName);
|
||||
document.getElementById("notesLegend").addEventListener("blur", updateLegend);
|
||||
document.getElementById("notesPin").addEventListener("click", toggleNotesPin);
|
||||
document.getElementById("notesFocus").addEventListener("click", validateHighlightElement);
|
||||
document.getElementById("notesDownload").addEventListener("click", downloadLegends);
|
||||
document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click());
|
||||
document.getElementById("legendsToLoad").addEventListener("change", function () {
|
||||
byId("notesSelect").addEventListener("change", changeElement);
|
||||
byId("notesName").addEventListener("input", changeName);
|
||||
byId("notesLegend").addEventListener("blur", updateLegend);
|
||||
byId("notesPin").addEventListener("click", toggleNotesPin);
|
||||
byId("notesFocus").addEventListener("click", validateHighlightElement);
|
||||
byId("notesDownload").addEventListener("click", downloadLegends);
|
||||
byId("notesUpload").addEventListener("click", () => legendsToLoad.click());
|
||||
byId("legendsToLoad").addEventListener("change", function () {
|
||||
uploadFile(this, uploadLegends);
|
||||
});
|
||||
document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove);
|
||||
byId("notesRemove").addEventListener("click", triggerNotesRemove);
|
||||
|
||||
async function initEditor() {
|
||||
if (!window.tinymce) {
|
||||
|
|
@ -108,8 +106,8 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
function updateNotesBox(note) {
|
||||
document.getElementById("notesHeader").innerHTML = note.name;
|
||||
document.getElementById("notesBody").innerHTML = note.legend;
|
||||
byId("notesHeader").innerHTML = note.name;
|
||||
byId("notesBody").innerHTML = note.legend;
|
||||
}
|
||||
|
||||
function changeElement() {
|
||||
|
|
@ -131,7 +129,7 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
function validateHighlightElement() {
|
||||
const element = document.getElementById(notesSelect.value);
|
||||
const element = byId(notesSelect.value);
|
||||
if (element) return highlightElement(element, 3);
|
||||
|
||||
confirmationDialog({
|
||||
|
|
@ -157,6 +155,18 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
function triggerNotesRemove() {
|
||||
function removeLegend() {
|
||||
notes = notes.filter(({id}) => id !== notesSelect.value);
|
||||
|
||||
if (!notes.length) {
|
||||
$("#notesEditor").dialog("close");
|
||||
return;
|
||||
}
|
||||
|
||||
removeEditor();
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
||||
confirmationDialog({
|
||||
title: "Remove note",
|
||||
message: "Are you sure you want to remove the selected note? There is no way to undo this action",
|
||||
|
|
@ -165,17 +175,6 @@ function editNotes(id, name) {
|
|||
});
|
||||
}
|
||||
|
||||
function removeLegend() {
|
||||
const index = notes.findIndex(n => n.id === notesSelect.value);
|
||||
notes.splice(index, 1);
|
||||
notesSelect.options.length = 0;
|
||||
if (!notes.length) {
|
||||
$("#notesEditor").dialog("close");
|
||||
return;
|
||||
}
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
||||
function toggleNotesPin() {
|
||||
options.pinNotes = !options.pinNotes;
|
||||
this.classList.toggle("pressed");
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function showOptions(event) {
|
|||
}
|
||||
|
||||
regenerate.style.display = "none";
|
||||
document.getElementById("options").style.display = "block";
|
||||
byId("options").style.display = "block";
|
||||
optionsTrigger.style.display = "none";
|
||||
|
||||
if (event) event.stopPropagation();
|
||||
|
|
@ -28,21 +28,21 @@ function showOptions(event) {
|
|||
|
||||
// Hide options pane on trigger click
|
||||
function hideOptions(event) {
|
||||
document.getElementById("options").style.display = "none";
|
||||
byId("options").style.display = "none";
|
||||
optionsTrigger.style.display = "block";
|
||||
if (event) event.stopPropagation();
|
||||
}
|
||||
|
||||
// To toggle options on hotkey press
|
||||
function toggleOptions(event) {
|
||||
if (document.getElementById("options").style.display === "none") showOptions(event);
|
||||
if (byId("options").style.display === "none") showOptions(event);
|
||||
else hideOptions(event);
|
||||
}
|
||||
|
||||
// Toggle "New Map!" pane on hover
|
||||
optionsTrigger.addEventListener("mouseenter", function () {
|
||||
if (optionsTrigger.classList.contains("glow")) return;
|
||||
if (document.getElementById("options").style.display === "none") regenerate.style.display = "block";
|
||||
if (byId("options").style.display === "none") regenerate.style.display = "block";
|
||||
});
|
||||
|
||||
collapsible.addEventListener("mouseleave", function () {
|
||||
|
|
@ -56,11 +56,11 @@ document
|
|||
.addEventListener("click", function (event) {
|
||||
if (event.target.tagName !== "BUTTON") return;
|
||||
const id = event.target.id;
|
||||
const active = document.getElementById("options").querySelector(".tab > button.active");
|
||||
const active = byId("options").querySelector(".tab > button.active");
|
||||
if (active && id === active.id) return; // already active tab is clicked
|
||||
|
||||
if (active) active.classList.remove("active");
|
||||
document.getElementById(id).classList.add("active");
|
||||
byId(id).classList.add("active");
|
||||
document
|
||||
.getElementById("options")
|
||||
.querySelectorAll(".tabcontent")
|
||||
|
|
@ -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=1.93.03");
|
||||
const {supporters} = await import("../dynamic/supporters.js?v=1.93.08");
|
||||
const list = supporters.split("\n").sort();
|
||||
const columns = window.innerWidth < 800 ? 2 : 5;
|
||||
|
||||
|
|
@ -91,10 +91,10 @@ async function showSupporters() {
|
|||
}
|
||||
|
||||
// on any option or dialog change
|
||||
document.getElementById("options").addEventListener("change", storeValueIfRequired);
|
||||
document.getElementById("dialogs").addEventListener("change", storeValueIfRequired);
|
||||
document.getElementById("options").addEventListener("input", updateOutputToFollowInput);
|
||||
document.getElementById("dialogs").addEventListener("input", updateOutputToFollowInput);
|
||||
byId("options").addEventListener("change", storeValueIfRequired);
|
||||
byId("dialogs").addEventListener("change", storeValueIfRequired);
|
||||
byId("options").addEventListener("input", updateOutputToFollowInput);
|
||||
byId("dialogs").addEventListener("input", updateOutputToFollowInput);
|
||||
|
||||
function storeValueIfRequired(ev) {
|
||||
if (ev.target.dataset.stored) lock(ev.target.dataset.stored);
|
||||
|
|
@ -109,16 +109,16 @@ function updateOutputToFollowInput(ev) {
|
|||
|
||||
// generic case
|
||||
if (id.slice(-5) === "Input") {
|
||||
const output = document.getElementById(id.slice(0, -5) + "Output");
|
||||
const output = byId(id.slice(0, -5) + "Output");
|
||||
if (output) output.value = value;
|
||||
} else if (id.slice(-6) === "Output") {
|
||||
const input = document.getElementById(id.slice(0, -6) + "Input");
|
||||
const input = byId(id.slice(0, -6) + "Input");
|
||||
if (input) input.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Option listeners
|
||||
const optionsContent = document.getElementById("optionsContent");
|
||||
const optionsContent = byId("optionsContent");
|
||||
optionsContent.addEventListener("input", function (event) {
|
||||
const id = event.target.id;
|
||||
const value = event.target.value;
|
||||
|
|
@ -139,7 +139,7 @@ optionsContent.addEventListener("change", function (event) {
|
|||
|
||||
if (id === "zoomExtentMin" || id === "zoomExtentMax") changeZoomExtent(value);
|
||||
else if (id === "optionsSeed") generateMapWithSeed("seed change");
|
||||
else if (id === "uiSizeInput" || id === "uiSizeOutput") changeUIsize(value);
|
||||
else if (id === "uiSizeInput" || id === "uiSizeOutput") changeUiSize(value);
|
||||
else if (id === "shapeRendering") setRendering(value);
|
||||
else if (id === "yearInput") changeYear();
|
||||
else if (id === "eraInput") changeEra();
|
||||
|
|
@ -148,7 +148,7 @@ optionsContent.addEventListener("change", function (event) {
|
|||
|
||||
optionsContent.addEventListener("click", function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === "toggleFullscreen") toggleFullscreen();
|
||||
if (id === "restoreDefaultCanvasSize") restoreDefaultCanvasSize();
|
||||
else if (id === "optionsMapHistory") showSeedHistoryDialog();
|
||||
else if (id === "optionsCopySeed") copyMapURL();
|
||||
else if (id === "optionsEraRegenerate") regenerateEra();
|
||||
|
|
@ -165,7 +165,7 @@ function mapSizeInputChange() {
|
|||
const $mapWidthInput = byId("mapWidthInput");
|
||||
const $mapHeightInput = byId("mapHeightInput");
|
||||
|
||||
changeMapSize();
|
||||
fitMapToScreen();
|
||||
localStorage.setItem("mapWidth", $mapWidthInput.value);
|
||||
localStorage.setItem("mapHeight", $mapHeightInput.value);
|
||||
|
||||
|
|
@ -173,77 +173,66 @@ function mapSizeInputChange() {
|
|||
const tooHigh = +$mapHeightInput.value > window.innerHeight;
|
||||
|
||||
if (tooWide || tooHigh) {
|
||||
const message = `Canvas size is larger than actual window size (${window.innerWidth} x ${window.innerHeight}). It can affect the performance if you are going to create a new map`;
|
||||
const message = `Canvas size is larger than window size (${window.innerWidth} x ${window.innerHeight}). It can affect performance`;
|
||||
tip(message, false, "warn", 4000);
|
||||
}
|
||||
}
|
||||
|
||||
// change svg size on manual size change or window resize, do not change graph size
|
||||
function changeMapSize() {
|
||||
function restoreDefaultCanvasSize() {
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
localStorage.removeItem("mapHeight");
|
||||
localStorage.removeItem("mapWidth");
|
||||
fitMapToScreen();
|
||||
}
|
||||
|
||||
// on map creation
|
||||
function applyGraphSize() {
|
||||
graphWidth = +mapWidthInput.value;
|
||||
graphHeight = +mapHeightInput.value;
|
||||
|
||||
landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||
fogging.selectAll("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||
defs.select("mask#fog > rect").attr("width", graphWidth).attr("height", graphHeight);
|
||||
defs.select("mask#water > rect").attr("width", graphWidth).attr("height", graphHeight);
|
||||
}
|
||||
|
||||
// on generate, on load, on resize, on canvas size change
|
||||
function fitMapToScreen() {
|
||||
svgWidth = Math.min(+mapWidthInput.value, window.innerWidth);
|
||||
svgHeight = Math.min(+mapHeightInput.value, window.innerHeight);
|
||||
svg.attr("width", svgWidth).attr("height", svgHeight);
|
||||
|
||||
const maxWidth = Math.max(+mapWidthInput.value, graphWidth);
|
||||
const maxHeight = Math.max(+mapHeightInput.value, graphHeight);
|
||||
zoom.translateExtent([
|
||||
const zoomExtent = [
|
||||
[0, 0],
|
||||
[maxWidth, maxHeight]
|
||||
]);
|
||||
landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
|
||||
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
|
||||
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
|
||||
fogging.selectAll("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
|
||||
defs.select("mask#fog > rect").attr("width", maxWidth).attr("height", maxHeight);
|
||||
texture.select("image").attr("width", maxWidth).attr("height", maxHeight);
|
||||
[graphWidth, graphHeight]
|
||||
];
|
||||
|
||||
fitScaleBar();
|
||||
if (window.fitLegendBox) fitLegendBox();
|
||||
}
|
||||
|
||||
// just apply canvas size that was already set
|
||||
function applyMapSize() {
|
||||
const zoomMin = +zoomExtentMin.value;
|
||||
const zoomMin = rn(Math.max(svgWidth / graphWidth, svgHeight / graphHeight), 3);
|
||||
zoomExtentMin.value = zoomMin;
|
||||
const zoomMax = +zoomExtentMax.value;
|
||||
graphWidth = +mapWidthInput.value;
|
||||
graphHeight = +mapHeightInput.value;
|
||||
svgWidth = Math.min(graphWidth, window.innerWidth);
|
||||
svgHeight = Math.min(graphHeight, window.innerHeight);
|
||||
svg.attr("width", svgWidth).attr("height", svgHeight);
|
||||
zoom
|
||||
.translateExtent([
|
||||
[0, 0],
|
||||
[graphWidth, graphHeight]
|
||||
])
|
||||
.scaleExtent([zoomMin, zoomMax])
|
||||
.scaleTo(svg, zoomMin);
|
||||
}
|
||||
|
||||
function toggleFullscreen() {
|
||||
if (mapWidthInput.value != window.innerWidth || mapHeightInput.value != window.innerHeight) {
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
localStorage.removeItem("mapHeight");
|
||||
localStorage.removeItem("mapWidth");
|
||||
} else {
|
||||
mapWidthInput.value = graphWidth;
|
||||
mapHeightInput.value = graphHeight;
|
||||
}
|
||||
changeMapSize();
|
||||
zoom.translateExtent(zoomExtent).scaleExtent([zoomMin, zoomMax]).scaleTo(svg, zoomMin);
|
||||
|
||||
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||
if (window.fitLegendBox) fitLegendBox();
|
||||
}
|
||||
|
||||
function toggleTranslateExtent(el) {
|
||||
const on = (el.dataset.on = +!+el.dataset.on);
|
||||
if (on)
|
||||
if (on) {
|
||||
zoom.translateExtent([
|
||||
[-graphWidth / 2, -graphHeight / 2],
|
||||
[graphWidth * 1.5, graphHeight * 1.5]
|
||||
]);
|
||||
else
|
||||
} else {
|
||||
zoom.translateExtent([
|
||||
[0, 0],
|
||||
[graphWidth, graphHeight]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// add voice options
|
||||
|
|
@ -252,7 +241,7 @@ const voiceInterval = setInterval(function () {
|
|||
if (voices.length) clearInterval(voiceInterval);
|
||||
else return;
|
||||
|
||||
const select = document.getElementById("speakerVoice");
|
||||
const select = byId("speakerVoice");
|
||||
voices.forEach((voice, i) => {
|
||||
select.options.add(new Option(voice.name, i, false));
|
||||
});
|
||||
|
|
@ -266,7 +255,7 @@ function testSpeaker() {
|
|||
const speaker = new SpeechSynthesisUtterance(text);
|
||||
const voices = speechSynthesis.getVoices();
|
||||
if (voices.length) {
|
||||
const voiceId = +document.getElementById("speakerVoice").value;
|
||||
const voiceId = +byId("speakerVoice").value;
|
||||
speaker.voice = voices[voiceId];
|
||||
}
|
||||
speechSynthesis.speak(speaker);
|
||||
|
|
@ -307,12 +296,6 @@ function restoreSeed(id) {
|
|||
regeneratePrompt({seed});
|
||||
}
|
||||
|
||||
function restoreDefaultZoomExtent() {
|
||||
zoomExtentMin.value = 1;
|
||||
zoomExtentMax.value = 20;
|
||||
zoom.scaleExtent([1, 20]).scaleTo(svg, 1);
|
||||
}
|
||||
|
||||
function copyMapURL() {
|
||||
const locked = document.querySelectorAll("i.icon-lock").length; // check if some options are locked
|
||||
const search = `?seed=${optionsSeed.value}&width=${graphWidth}&height=${graphHeight}${
|
||||
|
|
@ -365,7 +348,7 @@ function changeCultureSet() {
|
|||
}
|
||||
|
||||
function changeEmblemShape(emblemShape) {
|
||||
const image = document.getElementById("emblemShapeImage");
|
||||
const image = byId("emblemShapeImage");
|
||||
const shapePath = window.COArenderer && COArenderer.shieldPaths[emblemShape];
|
||||
shapePath ? image.setAttribute("d", shapePath) : image.removeAttribute("d");
|
||||
|
||||
|
|
@ -374,7 +357,7 @@ function changeEmblemShape(emblemShape) {
|
|||
pack.cultures.filter(c => !c.removed).forEach(c => (c.shield = Cultures.getRandomShield()));
|
||||
|
||||
const rerenderCOA = (id, coa) => {
|
||||
const coaEl = document.getElementById(id);
|
||||
const coaEl = byId(id);
|
||||
if (!coaEl) return; // not rendered
|
||||
coaEl.remove();
|
||||
COArenderer.trigger(id, coa);
|
||||
|
|
@ -412,7 +395,7 @@ function changeStatesNumber(value) {
|
|||
labels.select("#countries").attr("data-size", Math.max(rn(18 - value / 6), 4));
|
||||
}
|
||||
|
||||
function changeUIsize(value) {
|
||||
function changeUiSize(value) {
|
||||
if (isNaN(+value) || +value < 0.5) return;
|
||||
|
||||
const max = getUImaxSize();
|
||||
|
|
@ -420,7 +403,7 @@ function changeUIsize(value) {
|
|||
|
||||
uiSizeInput.value = uiSizeOutput.value = value;
|
||||
document.getElementsByTagName("body")[0].style.fontSize = rn(value * 10, 2) + "px";
|
||||
document.getElementById("options").style.width = value * 300 + "px";
|
||||
byId("options").style.width = value * 300 + "px";
|
||||
}
|
||||
|
||||
function getUImaxSize() {
|
||||
|
|
@ -480,7 +463,7 @@ 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();
|
||||
byId("loadGoogleTranslateButton")?.remove();
|
||||
|
||||
// replace mapLayers underline <u> with bare text to avoid translation issue
|
||||
document
|
||||
|
|
@ -527,6 +510,12 @@ function changeZoomExtent(value) {
|
|||
zoom.scaleTo(svg, scale);
|
||||
}
|
||||
|
||||
function restoreDefaultZoomExtent() {
|
||||
zoomExtentMin.value = 1;
|
||||
zoomExtentMax.value = 20;
|
||||
zoom.scaleExtent([1, 20]).scaleTo(svg, 1);
|
||||
}
|
||||
|
||||
// restore options stored in localStorage
|
||||
function applyStoredOptions() {
|
||||
if (!stored("mapWidth") || !stored("mapHeight")) {
|
||||
|
|
@ -569,8 +558,8 @@ function applyStoredOptions() {
|
|||
if (stored("regions")) changeStatesNumber(stored("regions"));
|
||||
|
||||
uiSizeInput.max = uiSizeOutput.max = getUImaxSize();
|
||||
if (stored("uiSize")) changeUIsize(stored("uiSize"));
|
||||
else changeUIsize(minmax(rn(mapWidthInput.value / 1280, 1), 1, 2.5));
|
||||
if (stored("uiSize")) changeUiSize(stored("uiSize"));
|
||||
else changeUiSize(minmax(rn(mapWidthInput.value / 1280, 1), 1, 2.5));
|
||||
|
||||
// search params overwrite stored and default options
|
||||
const params = new URL(window.location.href).searchParams;
|
||||
|
|
@ -702,7 +691,7 @@ function changeEra() {
|
|||
}
|
||||
|
||||
async function openTemplateSelectionDialog() {
|
||||
const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.87.00");
|
||||
const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.96.00");
|
||||
HeightmapSelectionDialog.open();
|
||||
}
|
||||
|
||||
|
|
@ -713,7 +702,7 @@ function restoreDefaultOptions() {
|
|||
}
|
||||
|
||||
// Sticked menu Options listeners
|
||||
document.getElementById("sticked").addEventListener("click", function (event) {
|
||||
byId("sticked").addEventListener("click", function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === "newMapButton") regeneratePrompt();
|
||||
else if (id === "saveButton") showSavePane();
|
||||
|
|
@ -746,7 +735,7 @@ function regeneratePrompt(options) {
|
|||
}
|
||||
|
||||
function showSavePane() {
|
||||
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||
const sharableLinkContainer = byId("sharableLinkContainer");
|
||||
sharableLinkContainer.style.display = "none";
|
||||
|
||||
$("#saveMapData").dialog({
|
||||
|
|
@ -763,13 +752,13 @@ function showSavePane() {
|
|||
}
|
||||
|
||||
function copyLinkToClickboard() {
|
||||
const shrableLink = document.getElementById("sharableLink");
|
||||
const shrableLink = byId("sharableLink");
|
||||
const link = shrableLink.getAttribute("href");
|
||||
navigator.clipboard.writeText(link).then(() => tip("Link is copied to the clipboard", true, "success", 8000));
|
||||
}
|
||||
|
||||
function showExportPane() {
|
||||
document.getElementById("showLabels").checked = !hideLabels.checked;
|
||||
byId("showLabels").checked = !hideLabels.checked;
|
||||
|
||||
$("#exportMapData").dialog({
|
||||
title: "Export map data",
|
||||
|
|
@ -785,7 +774,7 @@ function showExportPane() {
|
|||
}
|
||||
|
||||
async function exportToJson(type) {
|
||||
const {exportToJson} = await import("../dynamic/export-json.js?v=1.93.03");
|
||||
const {exportToJson} = await import("../dynamic/export-json.js?v=1.96.00");
|
||||
exportToJson(type);
|
||||
}
|
||||
|
||||
|
|
@ -804,10 +793,10 @@ async function showLoadPane() {
|
|||
|
||||
// already connected to Dropbox: list saved maps
|
||||
if (Cloud.providers.dropbox.api) {
|
||||
document.getElementById("dropboxConnectButton").style.display = "none";
|
||||
document.getElementById("loadFromDropboxSelect").style.display = "block";
|
||||
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
|
||||
const fileSelect = document.getElementById("loadFromDropboxSelect");
|
||||
byId("dropboxConnectButton").style.display = "none";
|
||||
byId("loadFromDropboxSelect").style.display = "block";
|
||||
const loadFromDropboxButtons = byId("loadFromDropboxButtons");
|
||||
const fileSelect = byId("loadFromDropboxSelect");
|
||||
fileSelect.innerHTML = /* html */ `<option value="" disabled selected>Loading...</option>`;
|
||||
|
||||
const files = await Cloud.providers.dropbox.list();
|
||||
|
|
@ -832,9 +821,9 @@ async function showLoadPane() {
|
|||
}
|
||||
|
||||
// not connected to Dropbox: show connect button
|
||||
document.getElementById("dropboxConnectButton").style.display = "inline-block";
|
||||
document.getElementById("loadFromDropboxButtons").style.display = "none";
|
||||
document.getElementById("loadFromDropboxSelect").style.display = "none";
|
||||
byId("dropboxConnectButton").style.display = "inline-block";
|
||||
byId("loadFromDropboxButtons").style.display = "none";
|
||||
byId("loadFromDropboxSelect").style.display = "none";
|
||||
}
|
||||
|
||||
async function connectToDropbox() {
|
||||
|
|
@ -870,38 +859,27 @@ function loadURL() {
|
|||
}
|
||||
|
||||
// load map
|
||||
document.getElementById("mapToLoad").addEventListener("change", function () {
|
||||
byId("mapToLoad").addEventListener("change", function () {
|
||||
const fileToLoad = this.files[0];
|
||||
this.value = "";
|
||||
closeDialogs();
|
||||
uploadMap(fileToLoad);
|
||||
});
|
||||
|
||||
function openSaveTiles() {
|
||||
function openExportToPngTiles() {
|
||||
byId("tileStatus").innerHTML = "";
|
||||
closeDialogs();
|
||||
updateTilesOptions();
|
||||
const status = document.getElementById("tileStatus");
|
||||
status.innerHTML = "";
|
||||
let loading = null;
|
||||
|
||||
const inputs = document.getElementById("saveTilesScreen").querySelectorAll("input");
|
||||
const inputs = byId("exportToPngTilesScreen").querySelectorAll("input");
|
||||
inputs.forEach(input => input.addEventListener("input", updateTilesOptions));
|
||||
|
||||
$("#saveTilesScreen").dialog({
|
||||
$("#exportToPngTilesScreen").dialog({
|
||||
resizable: false,
|
||||
title: "Download tiles",
|
||||
width: "23em",
|
||||
buttons: {
|
||||
Download: function () {
|
||||
status.innerHTML = "Preparing for download...";
|
||||
setTimeout(() => (status.innerHTML = "Downloading. It may take some time."), 1000);
|
||||
loading = setInterval(() => (status.innerHTML += "."), 1000);
|
||||
saveTiles().then(() => {
|
||||
clearInterval(loading);
|
||||
status.innerHTML = /* html */ `Done. Check file in "Downloads" (crtl + J)`;
|
||||
setTimeout(() => (status.innerHTML = ""), 8000);
|
||||
});
|
||||
},
|
||||
Download: () => exportToPngTiles(),
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
|
|
@ -909,7 +887,6 @@ function openSaveTiles() {
|
|||
close: () => {
|
||||
inputs.forEach(input => input.removeEventListener("input", updateTilesOptions));
|
||||
debug.selectAll("*").remove();
|
||||
clearInterval(loading);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -921,10 +898,10 @@ function updateTilesOptions() {
|
|||
if (prev?.tagName === "INPUT") prev.value = this.value;
|
||||
}
|
||||
|
||||
const tileSize = document.getElementById("tileSize");
|
||||
const tilesX = +document.getElementById("tileColsOutput").value;
|
||||
const tilesY = +document.getElementById("tileRowsOutput").value;
|
||||
const scale = +document.getElementById("tileScaleOutput").value;
|
||||
const tileSize = byId("tileSize");
|
||||
const tilesX = +byId("tileColsOutput").value;
|
||||
const tilesY = +byId("tileRowsOutput").value;
|
||||
const scale = +byId("tileScaleOutput").value;
|
||||
|
||||
// calculate size
|
||||
const sizeX = graphWidth * scale * tilesX;
|
||||
|
|
@ -939,18 +916,22 @@ function updateTilesOptions() {
|
|||
const labels = [];
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
|
||||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (let y = 0, row = 0; y + tileH <= graphHeight; y += tileH, row++) {
|
||||
for (let x = 0, column = 1; x + tileW <= graphWidth; x += tileW, column++) {
|
||||
rects.push(`<rect x=${x} y=${y} width=${tileW} height=${tileH} />`);
|
||||
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${i}</text>`);
|
||||
const label = alphabet[row % alphabet.length] + column;
|
||||
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${label}</text>`);
|
||||
}
|
||||
}
|
||||
const rectsG = "<g fill='none' stroke='#000'>" + rects.join("") + "</g>";
|
||||
const labelsG =
|
||||
"<g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='24px'>" +
|
||||
labels.join("") +
|
||||
"</g>";
|
||||
debug.html(rectsG + labelsG);
|
||||
|
||||
debug.html(`
|
||||
<g fill='none' stroke='#000'>${rects.join("")}</g>
|
||||
<g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='18px'>${labels.join(
|
||||
""
|
||||
)}</g>
|
||||
`);
|
||||
}
|
||||
|
||||
// View mode
|
||||
|
|
@ -973,9 +954,9 @@ function enterStandardView() {
|
|||
heightmap3DView.classList.remove("pressed");
|
||||
viewStandard.classList.add("pressed");
|
||||
|
||||
if (!document.getElementById("canvas3d")) return;
|
||||
if (!byId("canvas3d")) return;
|
||||
ThreeD.stop();
|
||||
document.getElementById("canvas3d").remove();
|
||||
byId("canvas3d").remove();
|
||||
if (options3dUpdate.offsetParent) $("#options3d").dialog("close");
|
||||
if (preview3d.offsetParent) $("#preview3d").dialog("close");
|
||||
}
|
||||
|
|
@ -1002,13 +983,13 @@ async function enter3dView(type) {
|
|||
canvas.style.display = "block";
|
||||
canvas.onmouseenter = () => {
|
||||
const help =
|
||||
"Left mouse to change angle, middle mouse / mousewheel to zoom, right mouse to pan. <b>O</b> to toggle options";
|
||||
"Left mouse to change angle, middle mouse. Mousewheel to zoom. Right mouse or hold Shift to pan. <b>O</b> to toggle options";
|
||||
+canvas.dataset.hovered > 2 ? tip("") : tip(help);
|
||||
canvas.dataset.hovered = (+canvas.dataset.hovered | 0) + 1;
|
||||
};
|
||||
|
||||
if (type === "heightmap3DView") {
|
||||
document.getElementById("preview3d").appendChild(canvas);
|
||||
byId("preview3d").appendChild(canvas);
|
||||
$("#preview3d").dialog({
|
||||
title: "3D Preview",
|
||||
resizable: true,
|
||||
|
|
@ -1022,7 +1003,7 @@ async function enter3dView(type) {
|
|||
}
|
||||
|
||||
function resize3d() {
|
||||
const canvas = document.getElementById("canvas3d");
|
||||
const canvas = byId("canvas3d");
|
||||
canvas.width = parseFloat(preview3d.style.width);
|
||||
canvas.height = parseFloat(preview3d.style.height) - 2;
|
||||
ThreeD.redraw();
|
||||
|
|
@ -1045,33 +1026,32 @@ function toggle3dOptions() {
|
|||
if (modules.options3d) return;
|
||||
modules.options3d = true;
|
||||
|
||||
document.getElementById("options3dUpdate").addEventListener("click", ThreeD.update);
|
||||
document.getElementById("options3dSave").addEventListener("click", ThreeD.saveScreenshot);
|
||||
document.getElementById("options3dOBJSave").addEventListener("click", ThreeD.saveOBJ);
|
||||
byId("options3dUpdate").addEventListener("click", ThreeD.update);
|
||||
byId("options3dSave").addEventListener("click", ThreeD.saveScreenshot);
|
||||
byId("options3dOBJSave").addEventListener("click", ThreeD.saveOBJ);
|
||||
|
||||
document.getElementById("options3dScaleRange").addEventListener("input", changeHeightScale);
|
||||
document.getElementById("options3dScaleNumber").addEventListener("change", changeHeightScale);
|
||||
document.getElementById("options3dLightnessRange").addEventListener("input", changeLightness);
|
||||
document.getElementById("options3dLightnessNumber").addEventListener("change", changeLightness);
|
||||
document.getElementById("options3dSunX").addEventListener("change", changeSunPosition);
|
||||
document.getElementById("options3dSunY").addEventListener("change", changeSunPosition);
|
||||
document.getElementById("options3dSunZ").addEventListener("change", changeSunPosition);
|
||||
document.getElementById("options3dMeshSkinResolution").addEventListener("change", changeResolutionScale);
|
||||
document.getElementById("options3dMeshRotationRange").addEventListener("input", changeRotation);
|
||||
document.getElementById("options3dMeshRotationNumber").addEventListener("change", changeRotation);
|
||||
document.getElementById("options3dGlobeRotationRange").addEventListener("input", changeRotation);
|
||||
document.getElementById("options3dGlobeRotationNumber").addEventListener("change", changeRotation);
|
||||
document.getElementById("options3dMeshLabels3d").addEventListener("change", toggleLabels3d);
|
||||
document.getElementById("options3dMeshSkyMode").addEventListener("change", toggleSkyMode);
|
||||
document.getElementById("options3dMeshSky").addEventListener("input", changeColors);
|
||||
document.getElementById("options3dMeshWater").addEventListener("input", changeColors);
|
||||
document.getElementById("options3dGlobeResolution").addEventListener("change", changeResolution);
|
||||
// document.getElementById("options3dMeshWireframeMode").addEventListener("change",toggleWireframe3d);
|
||||
document.getElementById("options3dSunColor").addEventListener("input", changeSunColor);
|
||||
document.getElementById("options3dSubdivide").addEventListener("change", toggle3dSubdivision);
|
||||
byId("options3dScaleRange").addEventListener("input", changeHeightScale);
|
||||
byId("options3dScaleNumber").addEventListener("change", changeHeightScale);
|
||||
byId("options3dLightnessRange").addEventListener("input", changeLightness);
|
||||
byId("options3dLightnessNumber").addEventListener("change", changeLightness);
|
||||
byId("options3dSunX").addEventListener("change", changeSunPosition);
|
||||
byId("options3dSunY").addEventListener("change", changeSunPosition);
|
||||
byId("options3dMeshSkinResolution").addEventListener("change", changeResolutionScale);
|
||||
byId("options3dMeshRotationRange").addEventListener("input", changeRotation);
|
||||
byId("options3dMeshRotationNumber").addEventListener("change", changeRotation);
|
||||
byId("options3dGlobeRotationRange").addEventListener("input", changeRotation);
|
||||
byId("options3dGlobeRotationNumber").addEventListener("change", changeRotation);
|
||||
byId("options3dMeshLabels3d").addEventListener("change", toggleLabels3d);
|
||||
byId("options3dMeshSkyMode").addEventListener("change", toggleSkyMode);
|
||||
byId("options3dMeshSky").addEventListener("input", changeColors);
|
||||
byId("options3dMeshWater").addEventListener("input", changeColors);
|
||||
byId("options3dGlobeResolution").addEventListener("change", changeResolution);
|
||||
// byId("options3dMeshWireframeMode").addEventListener("change",toggleWireframe3d);
|
||||
byId("options3dSunColor").addEventListener("input", changeSunColor);
|
||||
byId("options3dSubdivide").addEventListener("change", toggle3dSubdivision);
|
||||
|
||||
function updateValues() {
|
||||
const globe = document.getElementById("canvas3d").dataset.type === "viewGlobe";
|
||||
const globe = byId("canvas3d").dataset.type === "viewGlobe";
|
||||
options3dMesh.style.display = globe ? "none" : "block";
|
||||
options3dGlobe.style.display = globe ? "block" : "none";
|
||||
options3dOBJSave.style.display = globe ? "none" : "inline-block";
|
||||
|
|
@ -1079,7 +1059,6 @@ function toggle3dOptions() {
|
|||
options3dLightnessRange.value = options3dLightnessNumber.value = ThreeD.options.lightness * 100;
|
||||
options3dSunX.value = ThreeD.options.sun.x;
|
||||
options3dSunY.value = ThreeD.options.sun.y;
|
||||
options3dSunZ.value = ThreeD.options.sun.z;
|
||||
options3dMeshRotationRange.value = options3dMeshRotationNumber.value = ThreeD.options.rotateMesh;
|
||||
options3dMeshSkinResolution.value = ThreeD.options.resolutionScale;
|
||||
options3dGlobeRotationRange.value = options3dGlobeRotationNumber.value = ThreeD.options.rotateGlobe;
|
||||
|
|
@ -1115,8 +1094,7 @@ function toggle3dOptions() {
|
|||
function changeSunPosition() {
|
||||
const x = +options3dSunX.value;
|
||||
const y = +options3dSunY.value;
|
||||
const z = +options3dSunZ.value;
|
||||
ThreeD.setSun(x, y, z);
|
||||
ThreeD.setSun(x, y);
|
||||
}
|
||||
|
||||
function changeRotation() {
|
||||
|
|
|
|||
|
|
@ -44,12 +44,14 @@ function editProvinces() {
|
|||
cl = el.classList,
|
||||
line = el.parentNode,
|
||||
p = +line.dataset.id;
|
||||
const stateId = pack.provinces[p].state;
|
||||
|
||||
if (el.tagName === "FILL-BOX") changeFill(el);
|
||||
else if (cl.contains("name")) editProvinceName(p);
|
||||
else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]);
|
||||
else if (cl.contains("icon-star-empty")) capitalZoomIn(p);
|
||||
else if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p);
|
||||
else if (cl.contains("icon-dot-circled")) overviewBurgs({stateId});
|
||||
else if (cl.contains("culturePopulation")) changePopulation(p);
|
||||
else if (cl.contains("icon-pin")) toggleFog(p, cl);
|
||||
else if (cl.contains("icon-trash-empty")) removeProvince(p);
|
||||
|
|
@ -71,9 +73,8 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function collectStatistics() {
|
||||
const cells = pack.cells,
|
||||
provinces = pack.provinces,
|
||||
burgs = pack.burgs;
|
||||
const {cells, provinces, burgs} = pack;
|
||||
|
||||
provinces.forEach(p => {
|
||||
if (!p.i || p.removed) return;
|
||||
p.area = p.rural = p.urban = 0;
|
||||
|
|
@ -107,16 +108,18 @@ function editProvinces() {
|
|||
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
// add line for each province
|
||||
function provincesEditorAddLines() {
|
||||
const unit = " " + getAreaUnit();
|
||||
const selectedState = +document.getElementById("provincesFilterState").value;
|
||||
let filtered = pack.provinces.filter(p => p.i && !p.removed); // all valid burgs
|
||||
if (selectedState != -1) filtered = filtered.filter(p => p.state === selectedState); // filtered by state
|
||||
body.innerHTML = "";
|
||||
let lines = "",
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
|
||||
let lines = "";
|
||||
let totalArea = 0;
|
||||
let totalPopulation = 0;
|
||||
let totalBurgs = 0;
|
||||
|
||||
for (const p of filtered) {
|
||||
const area = getArea(p.area);
|
||||
|
|
@ -128,6 +131,7 @@ function editProvinces() {
|
|||
rural
|
||||
)}; Urban population: ${si(urban)}`;
|
||||
totalPopulation += population;
|
||||
totalBurgs += p.burgs.length;
|
||||
|
||||
const stateName = pack.states[p.state].name;
|
||||
const capital = p.burg ? pack.burgs[p.burg].name : "";
|
||||
|
|
@ -144,6 +148,7 @@ function editProvinces() {
|
|||
data-state="${stateName}"
|
||||
data-area=${area}
|
||||
data-population=${population}
|
||||
data-burgs=${p.burgs.length}
|
||||
>
|
||||
<fill-box fill="${p.color}"></fill-box>
|
||||
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly />
|
||||
|
|
@ -163,6 +168,8 @@ function editProvinces() {
|
|||
${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""}
|
||||
</select>
|
||||
<input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled">
|
||||
<span data-tip="Click to overview province burgs" style="padding-right: 1px" class="icon-dot-circled pointer hide"></span>
|
||||
<div data-tip="Burgs count" class="provinceBurgs hide">${p.burgs.length}</div>
|
||||
<span data-tip="Province area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
|
|
@ -179,11 +186,12 @@ function editProvinces() {
|
|||
body.innerHTML = lines;
|
||||
|
||||
// update footer
|
||||
provincesFooterNumber.innerHTML = filtered.length;
|
||||
provincesFooterArea.innerHTML = filtered.length ? si(totalArea / filtered.length) + unit : 0 + unit;
|
||||
provincesFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
|
||||
provincesFooterArea.dataset.area = totalArea;
|
||||
provincesFooterPopulation.dataset.population = totalPopulation;
|
||||
byId("provincesFooterNumber").innerHTML = filtered.length;
|
||||
byId("provincesFooterBurgs").innerHTML = totalBurgs;
|
||||
byId("provincesFooterArea").innerHTML = filtered.length ? si(totalArea / filtered.length) + unit : 0 + unit;
|
||||
byId("provincesFooterPopulation").innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
|
||||
byId("provincesFooterArea").dataset.area = totalArea;
|
||||
byId("provincesFooterPopulation").dataset.population = totalPopulation;
|
||||
|
||||
body.querySelectorAll("div.states").forEach(el => {
|
||||
el.addEventListener("click", selectProvinceOnLineClick);
|
||||
|
|
@ -294,7 +302,7 @@ function editProvinces() {
|
|||
// move all burgs to a new state
|
||||
province.burgs.forEach(b => (burgs[b].state = newStateId));
|
||||
|
||||
// difine new state attributes
|
||||
// define new state attributes
|
||||
const {cell: center, culture} = burgs[burgId];
|
||||
const color = getRandomColor();
|
||||
const coa = province.coa;
|
||||
|
|
@ -501,6 +509,9 @@ function editProvinces() {
|
|||
applyOption(provinceNameEditorSelectForm, p.formName);
|
||||
document.getElementById("provinceNameEditorFull").value = p.fullName;
|
||||
|
||||
const cultureId = pack.cells.culture[p.center];
|
||||
document.getElementById("provinceCultureDisplay").innerText = pack.cultures[cultureId].name;
|
||||
|
||||
$("#provinceNameEditor").dialog({
|
||||
resizable: false,
|
||||
title: "Change province name",
|
||||
|
|
@ -520,12 +531,12 @@ function editProvinces() {
|
|||
modules.editProvinceName = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("provinceNameEditorShortCulture").addEventListener("click", regenerateShortNameCuture);
|
||||
document.getElementById("provinceNameEditorShortCulture").addEventListener("click", regenerateShortNameCulture);
|
||||
document.getElementById("provinceNameEditorShortRandom").addEventListener("click", regenerateShortNameRandom);
|
||||
document.getElementById("provinceNameEditorAddForm").addEventListener("click", addCustomForm);
|
||||
document.getElementById("provinceNameEditorFullRegenerate").addEventListener("click", regenerateFullName);
|
||||
|
||||
function regenerateShortNameCuture() {
|
||||
function regenerateShortNameCulture() {
|
||||
const province = +provinceNameEditor.dataset.province;
|
||||
const culture = pack.cells.culture[pack.provinces[province].center];
|
||||
const name = Names.getState(Names.getCultureShort(culture), culture);
|
||||
|
|
@ -576,12 +587,15 @@ function editProvinces() {
|
|||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
const totalBurgs = +byId("provincesFooterBurgs").innerText;
|
||||
const totalArea = +provincesFooterArea.dataset.area;
|
||||
const totalPopulation = +provincesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%";
|
||||
el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%";
|
||||
const {cells, burgs, area, population} = el.dataset;
|
||||
el.querySelector(".provinceBurgs").innerText = rn((+burgs / totalBurgs) * 100) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+area / totalArea) * 100) + "%";
|
||||
el.querySelector(".culturePopulation").innerHTML = rn((+population / totalPopulation) * 100) + "%";
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
|
|
@ -1064,10 +1078,7 @@ function editProvinces() {
|
|||
|
||||
function downloadProvincesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data =
|
||||
"Id,Province,Full Name,Form,State,Color,Capital,Area " +
|
||||
unit +
|
||||
",Total Population,Rural Population,Urban Population\n"; // headers
|
||||
let data = `Id,Province,Full Name,Form,State,Color,Capital,Area ${unit},Total Population,Rural Population,Urban Population,Burgs\n`; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
const key = parseInt(el.dataset.id);
|
||||
|
|
@ -1081,8 +1092,9 @@ function editProvinces() {
|
|||
data += el.dataset.capital + ",";
|
||||
data += el.dataset.area + ",";
|
||||
data += el.dataset.population + ",";
|
||||
data += `${Math.round(provincePack.rural * populationRate)},`;
|
||||
data += `${Math.round(provincePack.urban * populationRate * urbanization)}\n`;
|
||||
data += Math.round(provincePack.rural * populationRate) + ",";
|
||||
data += Math.round(provincePack.urban * populationRate * urbanization) + ",";
|
||||
data += el.dataset.burgs + "\n";
|
||||
});
|
||||
|
||||
const name = getFileName("Provinces") + ".csv";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
// add available filters to lists
|
||||
{
|
||||
const filters = Array.from(document.getElementById("filters").querySelectorAll("filter"));
|
||||
const filters = Array.from(byId("filters").querySelectorAll("filter"));
|
||||
const emptyOption = '<option value="" selected>None</option>';
|
||||
const options = filters.map(filter => {
|
||||
const id = filter.getAttribute("id");
|
||||
|
|
@ -12,8 +12,9 @@
|
|||
});
|
||||
const allOptions = emptyOption + options.join("");
|
||||
|
||||
document.getElementById("styleFilterInput").innerHTML = allOptions;
|
||||
document.getElementById("styleStatesBodyFilter").innerHTML = allOptions;
|
||||
byId("styleFilterInput").innerHTML = allOptions;
|
||||
byId("styleStatesBodyFilter").innerHTML = allOptions;
|
||||
byId("styleScaleBarBackgroundFilter").innerHTML = allOptions;
|
||||
}
|
||||
|
||||
// store some style inputs as options
|
||||
|
|
@ -31,45 +32,77 @@ function editStyle(element, group) {
|
|||
|
||||
styleElementSelect.classList.add("glow");
|
||||
if (group) styleGroupSelect.classList.add("glow");
|
||||
|
||||
setTimeout(() => {
|
||||
styleElementSelect.classList.remove("glow");
|
||||
if (group) styleGroupSelect.classList.remove("glow");
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Color schemes
|
||||
const heightmapColorSchemes = {
|
||||
bright: d3.scaleSequential(d3.interpolateSpectral),
|
||||
light: d3.scaleSequential(d3.interpolateRdYlGn),
|
||||
natural: d3.scaleSequential(d3.interpolateRgbBasis(["white", "#EEEECC", "tan", "green", "teal"])),
|
||||
green: d3.scaleSequential(d3.interpolateGreens),
|
||||
olive: d3.scaleSequential(d3.interpolateRgbBasis(["#ffffff", "#cea48d", "#d5b085", "#0c2c19", "#151320"])),
|
||||
livid: d3.scaleSequential(d3.interpolateRgbBasis(["#BBBBDD", "#2A3440", "#17343B", "#0A1E24"])),
|
||||
monochrome: d3.scaleSequential(d3.interpolateGreys)
|
||||
};
|
||||
|
||||
// add default color schemes to the list of options
|
||||
byId("styleHeightmapScheme").innerHTML = Object.keys(heightmapColorSchemes)
|
||||
.map(scheme => `<option value="${scheme}">${scheme}</option>`)
|
||||
.join("");
|
||||
|
||||
function addCustomColorScheme(scheme) {
|
||||
const stops = scheme.split(",");
|
||||
heightmapColorSchemes[scheme] = d3.scaleSequential(d3.interpolateRgbBasis(stops));
|
||||
byId("styleHeightmapScheme").options.add(new Option(scheme, scheme, false, true));
|
||||
}
|
||||
|
||||
function getColorScheme(scheme = "bright") {
|
||||
if (!(scheme in heightmapColorSchemes)) {
|
||||
const colors = scheme.split(",");
|
||||
heightmapColorSchemes[scheme] = d3.scaleSequential(d3.interpolateRgbBasis(colors));
|
||||
}
|
||||
|
||||
return heightmapColorSchemes[scheme];
|
||||
}
|
||||
|
||||
// Toggle style sections on element select
|
||||
styleElementSelect.addEventListener("change", selectStyleElement);
|
||||
function selectStyleElement() {
|
||||
const sel = styleElementSelect.value;
|
||||
let el = d3.select("#" + sel);
|
||||
const styleElement = styleElementSelect.value;
|
||||
let el = d3.select("#" + styleElement);
|
||||
|
||||
styleElements.querySelectorAll("tbody").forEach(e => (e.style.display = "none")); // hide all sections
|
||||
|
||||
// show alert line if layer is not visible
|
||||
const isLayerOff = sel !== "ocean" && (el.style("display") === "none" || !el.selectAll("*").size());
|
||||
const isLayerOff = styleElement !== "ocean" && (el.style("display") === "none" || !el.selectAll("*").size());
|
||||
styleIsOff.style.display = isLayerOff ? "block" : "none";
|
||||
|
||||
// active group element
|
||||
const group = styleGroupSelect.value;
|
||||
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(sel)) {
|
||||
const gEl = group && el.select("#" + group);
|
||||
el = group && gEl.size() ? gEl : el.select("g");
|
||||
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
|
||||
const group = styleGroupSelect.value;
|
||||
const defaultGroupSelector = styleElement === "terrs" ? "#landHeights" : "g";
|
||||
el = group && el.select("#" + group).size() ? el.select("#" + group) : el.select(defaultGroupSelector);
|
||||
}
|
||||
|
||||
// opacity
|
||||
if (!["landmass", "ocean", "regions", "legend"].includes(sel)) {
|
||||
if (!["landmass", "ocean", "regions", "legend"].includes(styleElement)) {
|
||||
styleOpacity.style.display = "block";
|
||||
styleOpacityInput.value = styleOpacityOutput.value = el.attr("opacity") || 1;
|
||||
}
|
||||
|
||||
// filter
|
||||
if (!["landmass", "legend", "regions"].includes(sel)) {
|
||||
if (!["landmass", "legend", "regions", "scaleBar"].includes(styleElement)) {
|
||||
styleFilter.style.display = "block";
|
||||
styleFilterInput.value = el.attr("filter") || "";
|
||||
}
|
||||
|
||||
// fill
|
||||
if (["rivers", "lakes", "landmass", "prec", "ice", "fogging"].includes(sel)) {
|
||||
if (["rivers", "lakes", "landmass", "prec", "ice", "fogging", "scaleBar", "vignette"].includes(styleElement)) {
|
||||
styleFill.style.display = "block";
|
||||
styleFillInput.value = styleFillOutput.value = el.attr("fill");
|
||||
}
|
||||
|
|
@ -91,7 +124,7 @@ function selectStyleElement() {
|
|||
"coordinates",
|
||||
"zones",
|
||||
"gridOverlay"
|
||||
].includes(sel)
|
||||
].includes(styleElement)
|
||||
) {
|
||||
styleStroke.style.display = "block";
|
||||
styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke");
|
||||
|
|
@ -101,7 +134,9 @@ function selectStyleElement() {
|
|||
|
||||
// stroke dash
|
||||
if (
|
||||
["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(sel)
|
||||
["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(
|
||||
styleElement
|
||||
)
|
||||
) {
|
||||
styleStrokeDash.style.display = "block";
|
||||
styleStrokeDasharrayInput.value = el.attr("stroke-dasharray") || "";
|
||||
|
|
@ -121,30 +156,38 @@ function selectStyleElement() {
|
|||
"texture",
|
||||
"biomes",
|
||||
"zones"
|
||||
].includes(sel)
|
||||
].includes(styleElement)
|
||||
) {
|
||||
styleClipping.style.display = "block";
|
||||
styleClippingInput.value = el.attr("mask") || "";
|
||||
}
|
||||
|
||||
// show specific sections
|
||||
if (sel === "texture") styleTexture.style.display = "block";
|
||||
|
||||
if (sel === "terrs") {
|
||||
styleHeightmap.style.display = "block";
|
||||
styleHeightmapScheme.value = terrs.attr("scheme");
|
||||
styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = terrs.attr("terracing");
|
||||
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = terrs.attr("skip");
|
||||
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = terrs.attr("relax");
|
||||
styleHeightmapCurve.value = terrs.attr("curve");
|
||||
if (styleElement === "texture") {
|
||||
styleTexture.style.display = "block";
|
||||
styleTextureShiftX.value = el.attr("data-x") || 0;
|
||||
styleTextureShiftY.value = el.attr("data-y") || 0;
|
||||
updateTextureSelectValue(el.attr("data-href"));
|
||||
}
|
||||
|
||||
if (sel === "markers") {
|
||||
if (styleElement === "terrs") {
|
||||
styleHeightmap.style.display = "block";
|
||||
styleHeightmapRenderOceanOption.style.display = el.attr("id") === "oceanHeights" ? "block" : "none";
|
||||
styleHeightmapRenderOcean.checked = +el.attr("data-render");
|
||||
|
||||
styleHeightmapScheme.value = el.attr("scheme");
|
||||
styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = el.attr("terracing");
|
||||
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = el.attr("skip");
|
||||
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = el.attr("relax");
|
||||
styleHeightmapCurve.value = el.attr("curve");
|
||||
}
|
||||
|
||||
if (styleElement === "markers") {
|
||||
styleMarkers.style.display = "block";
|
||||
styleRescaleMarkers.checked = +markers.attr("rescale");
|
||||
}
|
||||
|
||||
if (sel === "gridOverlay") {
|
||||
if (styleElement === "gridOverlay") {
|
||||
styleGrid.style.display = "block";
|
||||
styleGridType.value = el.attr("type");
|
||||
styleGridScale.value = el.attr("scale") || 1;
|
||||
|
|
@ -153,7 +196,7 @@ function selectStyleElement() {
|
|||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
if (sel === "compass") {
|
||||
if (styleElement === "compass") {
|
||||
styleCompass.style.display = "block";
|
||||
const tr = parseTransform(compass.select("use").attr("transform"));
|
||||
styleCompassShiftX.value = tr[0];
|
||||
|
|
@ -161,14 +204,14 @@ function selectStyleElement() {
|
|||
styleCompassSizeInput.value = styleCompassSizeOutput.value = tr[2];
|
||||
}
|
||||
|
||||
if (sel === "terrain") {
|
||||
if (styleElement === "terrain") {
|
||||
styleRelief.style.display = "block";
|
||||
styleReliefSizeOutput.innerHTML = styleReliefSizeInput.value = terrain.attr("size");
|
||||
styleReliefDensityOutput.innerHTML = styleReliefDensityInput.value = terrain.attr("density");
|
||||
styleReliefSet.value = terrain.attr("set");
|
||||
}
|
||||
|
||||
if (sel === "population") {
|
||||
if (styleElement === "population") {
|
||||
stylePopulation.style.display = "block";
|
||||
stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population
|
||||
.select("#rural")
|
||||
|
|
@ -180,7 +223,7 @@ function selectStyleElement() {
|
|||
styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || "";
|
||||
}
|
||||
|
||||
if (sel === "regions") {
|
||||
if (styleElement === "regions") {
|
||||
styleStates.style.display = "block";
|
||||
styleStatesBodyOpacity.value = styleStatesBodyOpacityOutput.value = statesBody.attr("opacity") || 1;
|
||||
styleStatesBodyFilter.value = statesBody.attr("filter") || "";
|
||||
|
|
@ -190,7 +233,7 @@ function selectStyleElement() {
|
|||
styleStatesHaloBlur.value = styleStatesHaloBlurOutput.value = blur;
|
||||
}
|
||||
|
||||
if (sel === "labels") {
|
||||
if (styleElement === "labels") {
|
||||
styleFill.style.display = "block";
|
||||
styleStroke.style.display = "block";
|
||||
styleStrokeWidth.style.display = "block";
|
||||
|
|
@ -208,7 +251,7 @@ function selectStyleElement() {
|
|||
styleFontSize.value = el.attr("data-size");
|
||||
}
|
||||
|
||||
if (sel === "provs") {
|
||||
if (styleElement === "provs") {
|
||||
styleFill.style.display = "block";
|
||||
styleSize.style.display = "block";
|
||||
styleFillInput.value = styleFillOutput.value = el.attr("fill") || "#111111";
|
||||
|
|
@ -218,7 +261,7 @@ function selectStyleElement() {
|
|||
styleFontSize.value = el.attr("data-size");
|
||||
}
|
||||
|
||||
if (sel == "burgIcons") {
|
||||
if (styleElement == "burgIcons") {
|
||||
styleFill.style.display = "block";
|
||||
styleStroke.style.display = "block";
|
||||
styleStrokeWidth.style.display = "block";
|
||||
|
|
@ -232,7 +275,7 @@ function selectStyleElement() {
|
|||
styleRadiusInput.value = el.attr("size") || 1;
|
||||
}
|
||||
|
||||
if (sel == "anchors") {
|
||||
if (styleElement == "anchors") {
|
||||
styleFill.style.display = "block";
|
||||
styleStroke.style.display = "block";
|
||||
styleStrokeWidth.style.display = "block";
|
||||
|
|
@ -243,7 +286,7 @@ function selectStyleElement() {
|
|||
styleIconSizeInput.value = el.attr("size") || 2;
|
||||
}
|
||||
|
||||
if (sel === "legend") {
|
||||
if (styleElement === "legend") {
|
||||
styleStroke.style.display = "block";
|
||||
styleStrokeWidth.style.display = "block";
|
||||
styleSize.style.display = "block";
|
||||
|
|
@ -261,16 +304,16 @@ function selectStyleElement() {
|
|||
styleFontSize.value = el.attr("data-size");
|
||||
}
|
||||
|
||||
if (sel === "ocean") {
|
||||
if (styleElement === "ocean") {
|
||||
styleOcean.style.display = "block";
|
||||
styleOceanFill.value = styleOceanFillOutput.value = oceanLayers.select("#oceanBase").attr("fill");
|
||||
styleOceanPattern.value = document.getElementById("oceanicPattern")?.getAttribute("href");
|
||||
styleOceanPattern.value = byId("oceanicPattern")?.getAttribute("href");
|
||||
styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value =
|
||||
document.getElementById("oceanicPattern").getAttribute("opacity") || 1;
|
||||
byId("oceanicPattern").getAttribute("opacity") || 1;
|
||||
outlineLayers.value = oceanLayers.attr("layers");
|
||||
}
|
||||
|
||||
if (sel === "temperature") {
|
||||
if (styleElement === "temperature") {
|
||||
styleStrokeWidth.style.display = "block";
|
||||
styleTemperature.style.display = "block";
|
||||
styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || "";
|
||||
|
|
@ -279,18 +322,18 @@ function selectStyleElement() {
|
|||
styleTemperatureFontSizeInput.value = styleTemperatureFontSizeOutput.value = el.attr("font-size") || "8px";
|
||||
}
|
||||
|
||||
if (sel === "coordinates") {
|
||||
if (styleElement === "coordinates") {
|
||||
styleSize.style.display = "block";
|
||||
styleFontSize.value = el.attr("data-size");
|
||||
}
|
||||
|
||||
if (sel === "armies") {
|
||||
if (styleElement === "armies") {
|
||||
styleArmies.style.display = "block";
|
||||
styleArmiesFillOpacity.value = styleArmiesFillOpacityOutput.value = el.attr("fill-opacity");
|
||||
styleArmiesSize.value = styleArmiesSizeOutput.value = el.attr("box-size");
|
||||
}
|
||||
|
||||
if (sel === "emblems") {
|
||||
if (styleElement === "emblems") {
|
||||
styleEmblems.style.display = "block";
|
||||
styleStrokeWidth.style.display = "block";
|
||||
styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || 1;
|
||||
|
|
@ -298,8 +341,8 @@ function selectStyleElement() {
|
|||
|
||||
// update group options
|
||||
styleGroupSelect.options.length = 0; // remove all options
|
||||
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(sel)) {
|
||||
const groups = document.getElementById(sel).querySelectorAll("g");
|
||||
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
|
||||
const groups = byId(styleElement).querySelectorAll("g");
|
||||
groups.forEach(el => {
|
||||
if (el.id === "burgLabels") return;
|
||||
const option = new Option(`${el.id} (${el.childElementCount})`, el.id, false, false);
|
||||
|
|
@ -308,15 +351,56 @@ function selectStyleElement() {
|
|||
styleGroupSelect.value = el.attr("id");
|
||||
styleGroup.style.display = "block";
|
||||
} else {
|
||||
styleGroupSelect.options.add(new Option(sel, sel, false, true));
|
||||
styleGroupSelect.options.add(new Option(styleElement, styleElement, false, true));
|
||||
styleGroup.style.display = "none";
|
||||
}
|
||||
|
||||
if (sel === "coastline" && styleGroupSelect.value === "sea_island") {
|
||||
if (styleElement === "coastline" && styleGroupSelect.value === "sea_island") {
|
||||
styleCoastline.style.display = "block";
|
||||
const auto = (styleCoastlineAuto.checked = coastline.select("#sea_island").attr("auto-filter"));
|
||||
if (auto) styleFilter.style.display = "none";
|
||||
}
|
||||
|
||||
if (styleElement === "scaleBar") {
|
||||
styleScaleBar.style.display = "block";
|
||||
|
||||
styleScaleBarSize.value = el.attr("data-bar-size");
|
||||
styleScaleBarFontSize.value = el.attr("font-size");
|
||||
styleScaleBarPositionX.value = el.attr("data-x") || "99";
|
||||
styleScaleBarPositionY.value = el.attr("data-y") || "99";
|
||||
styleScaleBarLabel.value = el.attr("data-label") || "";
|
||||
|
||||
const scaleBarBack = el.select("#scaleBarBack");
|
||||
if (scaleBarBack.size()) {
|
||||
styleScaleBarBackgroundOpacityInput.value = styleScaleBarBackgroundOpacityOutput.value =
|
||||
scaleBarBack.attr("opacity");
|
||||
styleScaleBarBackgroundFillInput.value = styleScaleBarBackgroundFillOutput.value = scaleBarBack.attr("fill");
|
||||
styleScaleBarBackgroundStrokeInput.value = styleScaleBarBackgroundStrokeOutput.value =
|
||||
scaleBarBack.attr("stroke");
|
||||
styleScaleBarBackgroundStrokeWidth.value = scaleBarBack.attr("stroke-width");
|
||||
styleScaleBarBackgroundFilter.value = scaleBarBack.attr("filter");
|
||||
styleScaleBarBackgroundPaddingTop.value = scaleBarBack.attr("data-top");
|
||||
styleScaleBarBackgroundPaddingRight.value = scaleBarBack.attr("data-right");
|
||||
styleScaleBarBackgroundPaddingBottom.value = scaleBarBack.attr("data-bottom");
|
||||
styleScaleBarBackgroundPaddingLeft.value = scaleBarBack.attr("data-left");
|
||||
}
|
||||
}
|
||||
|
||||
if (styleElement === "vignette") {
|
||||
styleVignette.style.display = "block";
|
||||
|
||||
const maskRect = byId("vignette-rect");
|
||||
if (maskRect) {
|
||||
const digit = str => str.replace(/[^\d.]/g, "");
|
||||
styleVignetteX.value = digit(maskRect.getAttribute("x"));
|
||||
styleVignetteY.value = digit(maskRect.getAttribute("y"));
|
||||
styleVignetteWidth.value = digit(maskRect.getAttribute("width"));
|
||||
styleVignetteHeight.value = digit(maskRect.getAttribute("height"));
|
||||
styleVignetteRx.value = digit(maskRect.getAttribute("rx"));
|
||||
styleVignetteRy.value = digit(maskRect.getAttribute("ry"));
|
||||
styleVignetteBlur.value = styleVignetteBlurOutput.value = digit(maskRect.getAttribute("filter"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle style inputs change
|
||||
|
|
@ -367,12 +451,26 @@ styleFilterInput.addEventListener("change", function () {
|
|||
});
|
||||
|
||||
styleTextureInput.addEventListener("change", function () {
|
||||
texture.select("image").attr("src", this.value);
|
||||
if (layerIsOn("toggleTexture")) texture.select("image").attr("href", this.value);
|
||||
zoom.scaleBy(svg, 1.00001);
|
||||
changeTexture(this.value);
|
||||
});
|
||||
|
||||
function changeTexture(href) {
|
||||
texture.attr("data-href", href);
|
||||
texture.select("image").attr("href", href);
|
||||
}
|
||||
|
||||
function updateTextureSelectValue(href) {
|
||||
const isAdded = Array.from(styleTextureInput.options).some(option => option.value === href);
|
||||
if (isAdded) {
|
||||
styleTextureInput.value = href;
|
||||
} else {
|
||||
const name = href.split("/").pop().slice(0, 20);
|
||||
styleTextureInput.add(new Option(name, href, false, true));
|
||||
}
|
||||
}
|
||||
|
||||
styleTextureShiftX.addEventListener("input", function () {
|
||||
texture.attr("data-x", this.value);
|
||||
texture
|
||||
.select("image")
|
||||
.attr("x", this.value)
|
||||
|
|
@ -380,6 +478,7 @@ styleTextureShiftX.addEventListener("input", function () {
|
|||
});
|
||||
|
||||
styleTextureShiftY.addEventListener("input", function () {
|
||||
texture.attr("data-y", this.value);
|
||||
texture
|
||||
.select("image")
|
||||
.attr("y", this.value)
|
||||
|
|
@ -418,15 +517,6 @@ styleGridShiftY.addEventListener("input", function () {
|
|||
if (layerIsOn("toggleGrid")) drawGrid();
|
||||
});
|
||||
|
||||
styleShiftX.addEventListener("input", shiftElement);
|
||||
styleShiftY.addEventListener("input", shiftElement);
|
||||
|
||||
function shiftElement() {
|
||||
const x = styleShiftX.value || 0;
|
||||
const y = styleShiftY.value || 0;
|
||||
getEl().attr("transform", `translate(${x},${y})`);
|
||||
}
|
||||
|
||||
styleRescaleMarkers.addEventListener("change", function () {
|
||||
markers.attr("rescale", +this.checked);
|
||||
invokeActiveZooming();
|
||||
|
|
@ -444,11 +534,11 @@ styleOceanFill.addEventListener("input", function () {
|
|||
});
|
||||
|
||||
styleOceanPattern.addEventListener("change", function () {
|
||||
document.getElementById("oceanicPattern")?.setAttribute("href", this.value);
|
||||
byId("oceanicPattern")?.setAttribute("href", this.value);
|
||||
});
|
||||
|
||||
styleOceanPatternOpacity.addEventListener("input", function () {
|
||||
document.getElementById("oceanicPattern").setAttribute("opacity", this.value);
|
||||
byId("oceanicPattern").setAttribute("opacity", this.value);
|
||||
styleOceanPatternOpacityOutput.value = this.value;
|
||||
});
|
||||
|
||||
|
|
@ -459,27 +549,151 @@ outlineLayers.addEventListener("change", function () {
|
|||
});
|
||||
|
||||
styleHeightmapScheme.addEventListener("change", function () {
|
||||
terrs.attr("scheme", this.value);
|
||||
getEl().attr("scheme", this.value);
|
||||
drawHeightmap();
|
||||
});
|
||||
|
||||
openCreateHeightmapSchemeButton.addEventListener("click", function () {
|
||||
// start with current scheme
|
||||
const scheme = getEl().attr("scheme");
|
||||
this.dataset.stops = scheme.startsWith("#")
|
||||
? scheme
|
||||
: (() => [0, 0.25, 0.5, 0.75, 1].map(heightmapColorSchemes[scheme]).map(toHEX).join(","))();
|
||||
|
||||
// render dialog base structure
|
||||
alertMessage.innerHTML = /* html */ `<div>
|
||||
<i>Define heightmap gradient colors from high to low altitude</i>
|
||||
<img id="heightmapSchemePreview" alt="heightmap preview" style="margin-top: 0.5em; width: 100%;" />
|
||||
<div id="heightmapSchemeStops" style="margin-block: 0.5em; display: flex; flex-wrap: wrap;"></div>
|
||||
<div id="heightmapSchemeGradient" style="height: 1.9em; border: 1px solid #767676;"></div>
|
||||
</div>`;
|
||||
|
||||
renderPreview();
|
||||
renderStops();
|
||||
renderGradient();
|
||||
|
||||
function renderPreview() {
|
||||
const stops = openCreateHeightmapSchemeButton.dataset.stops.split(",");
|
||||
const scheme = d3.scaleSequential(d3.interpolateRgbBasis(stops));
|
||||
|
||||
const preview = drawHeights({
|
||||
heights: grid.cells.h,
|
||||
width: grid.cellsX,
|
||||
height: grid.cellsY,
|
||||
scheme,
|
||||
renderOcean: false
|
||||
});
|
||||
|
||||
byId("heightmapSchemePreview").src = preview;
|
||||
}
|
||||
|
||||
function renderStops() {
|
||||
const stops = openCreateHeightmapSchemeButton.dataset.stops.split(",");
|
||||
|
||||
const colorInput = color =>
|
||||
`<input type="color" class="stop" value="${color}" data-tip="Click to set the color" style="width: 2.5em; border: none;" />`;
|
||||
const removeStopButton = index =>
|
||||
`<button class="remove" data-index="${index}" data-tip="Remove color stop" style="margin-top: 0.3em; height: max-content;">x</button>`;
|
||||
const addStopButton = () =>
|
||||
`<button class="add" data-tip="Add color stop in between" style="margin-top: 0.3em; height: max-content;">+</button>`;
|
||||
|
||||
const container = byId("heightmapSchemeStops");
|
||||
container.innerHTML = stops
|
||||
.map(
|
||||
(stop, index) => `${colorInput(stop)}
|
||||
${index && index < stops.length - 1 ? removeStopButton(index) : ""}`
|
||||
)
|
||||
.join(addStopButton());
|
||||
|
||||
Array.from(container.querySelectorAll("input.stop")).forEach(
|
||||
(input, index) =>
|
||||
(input.oninput = function () {
|
||||
stops[index] = this.value;
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderGradient();
|
||||
})
|
||||
);
|
||||
|
||||
Array.from(container.querySelectorAll("button.remove")).forEach(
|
||||
button =>
|
||||
(button.onclick = function () {
|
||||
const index = +this.dataset.index;
|
||||
stops.splice(index, 1);
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderStops();
|
||||
renderGradient();
|
||||
})
|
||||
);
|
||||
|
||||
Array.from(container.querySelectorAll("button.add")).forEach(
|
||||
(button, index) =>
|
||||
(button.onclick = function () {
|
||||
const middleColor = d3.interpolateRgb(stops[index], stops[index + 1])(0.5);
|
||||
stops.splice(index + 1, 0, toHEX(middleColor));
|
||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||
renderPreview();
|
||||
renderStops();
|
||||
renderGradient();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function renderGradient() {
|
||||
const stops = openCreateHeightmapSchemeButton.dataset.stops;
|
||||
byId("heightmapSchemeGradient").style.background = `linear-gradient(to right, ${stops})`;
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
const stops = openCreateHeightmapSchemeButton.dataset.stops;
|
||||
if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error");
|
||||
|
||||
addCustomColorScheme(stops);
|
||||
getEl().attr("scheme", stops);
|
||||
drawHeightmap();
|
||||
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
$("#alert").dialog("close");
|
||||
}
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Create heightmap color scheme",
|
||||
width: "28em",
|
||||
buttons: {
|
||||
Create: handleCreate,
|
||||
Cancel: handleClose
|
||||
},
|
||||
position: {my: "center top+150", at: "center top", of: "svg"}
|
||||
});
|
||||
});
|
||||
|
||||
styleHeightmapRenderOcean.addEventListener("change", function () {
|
||||
getEl().attr("data-render", +this.checked);
|
||||
drawHeightmap();
|
||||
});
|
||||
|
||||
styleHeightmapTerracingInput.addEventListener("input", function () {
|
||||
terrs.attr("terracing", this.value);
|
||||
getEl().attr("terracing", this.value);
|
||||
drawHeightmap();
|
||||
});
|
||||
|
||||
styleHeightmapSkipInput.addEventListener("input", function () {
|
||||
terrs.attr("skip", this.value);
|
||||
getEl().attr("skip", this.value);
|
||||
drawHeightmap();
|
||||
});
|
||||
|
||||
styleHeightmapSimplificationInput.addEventListener("input", function () {
|
||||
terrs.attr("relax", this.value);
|
||||
getEl().attr("relax", this.value);
|
||||
drawHeightmap();
|
||||
});
|
||||
|
||||
styleHeightmapCurve.addEventListener("change", function () {
|
||||
terrs.attr("curve", this.value);
|
||||
getEl().attr("curve", this.value);
|
||||
drawHeightmap();
|
||||
});
|
||||
|
||||
|
|
@ -753,27 +967,19 @@ emblemsBurgSizeInput.addEventListener("change", drawEmblems);
|
|||
|
||||
// request a URL to image to be used as a texture
|
||||
function textureProvideURL() {
|
||||
alertMessage.innerHTML = /* html */ `Provide an image URL to be used as a texture:
|
||||
alertMessage.innerHTML = /* html */ `Provide a texture image URL:
|
||||
<input id="textureURL" type="url" style="width: 100%" placeholder="http://www.example.com/image.jpg" oninput="fetchTextureURL(this.value)" />
|
||||
<canvas id="texturePreview" width="256px" height="144px"></canvas>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Load custom texture",
|
||||
width: "26em",
|
||||
width: "28em",
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
const name = textureURL.value.split("/").pop();
|
||||
if (!name || name === "") return tip("Please provide a valid URL", false, "error");
|
||||
|
||||
const opt = document.createElement("option");
|
||||
opt.value = textureURL.value;
|
||||
opt.text = name.slice(0, 20);
|
||||
styleTextureInput.add(opt);
|
||||
styleTextureInput.value = textureURL.value;
|
||||
|
||||
const image = texture.select("image");
|
||||
image.attr("src", textureURL.value);
|
||||
if (layerIsOn("toggleTexture")) image.attr("href", textureURL.value);
|
||||
if (!textureURL.value) return tip("Please provide a valid URL", false, "error");
|
||||
changeTexture(textureURL.value);
|
||||
updateTextureSelectValue(textureURL.value);
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
|
|
@ -784,10 +990,10 @@ function textureProvideURL() {
|
|||
}
|
||||
|
||||
function fetchTextureURL(url) {
|
||||
INFO && console.log("Provided URL is", url);
|
||||
INFO && console.info("Provided URL is", url);
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
const canvas = document.getElementById("texturePreview");
|
||||
const canvas = byId("texturePreview");
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
|
|
@ -795,6 +1001,119 @@ function fetchTextureURL(url) {
|
|||
img.src = url;
|
||||
}
|
||||
|
||||
const vignettePresets = {
|
||||
default: `{ "#vignette": { "opacity": 0.3, "fill": "#000000", "filter": null }, "#vignette-rect": { "x": "0.3%", "y": "0.4%", "width": "99.6%", "height": "99.2%", "rx": "5%", "ry": "5%", "filter": "blur(20px)" } }`,
|
||||
neon: `{ "#vignette": { "opacity": 0.5, "fill": "#7300ff", "filter": null }, "#vignette-rect": { "x": "0.3%", "y": "0.4%", "width": "99.6%", "height": "99.2%", "rx": "0%", "ry": "0%", "filter": "blur(15px)" } }`,
|
||||
smoke: `{ "#vignette": { "opacity": 1, "fill": "#000000", "filter": "url(#splotch)" }, "#vignette-rect": { "x": "3%", "y": "5%", "width": "96%", "height": "90%", "rx": "10%", "ry": "10%", "filter": "blur(100px)" } }`,
|
||||
wound: `{ "#vignette": { "opacity": 0.8, "fill": "#ff0000", "filter": "url(#paper)"}, "#vignette-rect": {"x": "0.5%", "y": "1%", "width": "99%", "height": "98%", "rx": "5%", "ry": "5%", "filter": "blur(50px)" } }`,
|
||||
paper: `{ "#vignette": { "opacity": 1, "fill": "#000000", "filter": "url(#paper)" }, "#vignette-rect": { "x": "0.3%", "y": "0.4%", "width": "99.6%", "height": "99.2%", "rx": "20%", "ry": "20%", "filter": "blur(150px)" } }`,
|
||||
granite: `{ "#vignette": { "opacity": 0.95, "fill": "#231b1b", "filter": "url(#crumpled)" }, "#vignette-rect": { "x": "3%", "y": "5%", "width": "94%", "height": "90%", "rx": "20%", "ry": "20%", "filter": "blur(150px)" } }`,
|
||||
spotlight: `{ "#vignette": { "opacity": 0.96, "fill": "#000000", "filter": null }, "#vignette-rect": { "x": "20%", "y": "30%", "width": "24%", "height": "30%", "rx": "50%", "ry": "50%", "filter": "blur(30px) "} }`
|
||||
};
|
||||
|
||||
Object.keys(vignettePresets).forEach(preset => {
|
||||
styleVignettePreset.options.add(new Option(preset, preset, false, false));
|
||||
});
|
||||
|
||||
styleVignettePreset.addEventListener("change", function () {
|
||||
const attributes = JSON.parse(vignettePresets[this.value]);
|
||||
|
||||
for (const selector in attributes) {
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) continue;
|
||||
for (const attr in attributes[selector]) {
|
||||
const value = attributes[selector][attr];
|
||||
el.setAttribute(attr, value);
|
||||
}
|
||||
}
|
||||
|
||||
const vignette = byId("vignette");
|
||||
if (vignette) {
|
||||
styleOpacityInput.value = styleOpacityOutput.value = vignette.getAttribute("opacity");
|
||||
styleFillInput.value = styleFillOutput.value = vignette.getAttribute("fill");
|
||||
styleFilterInput.value = vignette.getAttribute("filter");
|
||||
}
|
||||
|
||||
const maskRect = byId("vignette-rect");
|
||||
if (maskRect) {
|
||||
const digit = str => str.replace(/[^\d.]/g, "");
|
||||
styleVignetteX.value = digit(maskRect.getAttribute("x"));
|
||||
styleVignetteY.value = digit(maskRect.getAttribute("y"));
|
||||
styleVignetteWidth.value = digit(maskRect.getAttribute("width"));
|
||||
styleVignetteHeight.value = digit(maskRect.getAttribute("height"));
|
||||
styleVignetteRx.value = digit(maskRect.getAttribute("rx"));
|
||||
styleVignetteRy.value = digit(maskRect.getAttribute("ry"));
|
||||
styleVignetteBlur.value = styleVignetteBlurOutput.value = digit(maskRect.getAttribute("filter"));
|
||||
}
|
||||
});
|
||||
|
||||
styleVignetteX.addEventListener("input", function () {
|
||||
byId("vignette-rect")?.setAttribute("x", `${this.value}%`);
|
||||
});
|
||||
|
||||
styleVignetteWidth.addEventListener("input", function () {
|
||||
byId("vignette-rect")?.setAttribute("width", `${this.value}%`);
|
||||
});
|
||||
|
||||
styleVignetteY.addEventListener("input", function () {
|
||||
byId("vignette-rect")?.setAttribute("y", `${this.value}%`);
|
||||
});
|
||||
|
||||
styleVignetteHeight.addEventListener("input", function () {
|
||||
byId("vignette-rect")?.setAttribute("height", `${this.value}%`);
|
||||
});
|
||||
|
||||
styleVignetteRx.addEventListener("input", function () {
|
||||
byId("vignette-rect")?.setAttribute("rx", `${this.value}%`);
|
||||
});
|
||||
|
||||
styleVignetteRy.addEventListener("input", function () {
|
||||
byId("vignette-rect")?.setAttribute("ry", `${this.value}%`);
|
||||
});
|
||||
|
||||
styleVignetteBlur.addEventListener("input", function () {
|
||||
styleVignetteBlurOutput.value = this.value;
|
||||
byId("vignette-rect")?.setAttribute("filter", `blur(${this.value}px)`);
|
||||
});
|
||||
|
||||
styleScaleBar.addEventListener("input", function (event) {
|
||||
const scaleBarBack = scaleBar.select("#scaleBarBack");
|
||||
if (!scaleBarBack.size()) return;
|
||||
|
||||
const {id, value} = event.target;
|
||||
|
||||
if (id === "styleScaleBarSize") scaleBar.attr("data-bar-size", value);
|
||||
else if (id === "styleScaleBarFontSize") scaleBar.attr("font-size", value);
|
||||
else if (id === "styleScaleBarPositionX") scaleBar.attr("data-x", value);
|
||||
else if (id === "styleScaleBarPositionY") scaleBar.attr("data-y", value);
|
||||
else if (id === "styleScaleBarLabel") scaleBar.attr("data-label", value);
|
||||
else if (id === "styleScaleBarBackgroundOpacityInput") scaleBarBack.attr("opacity", value);
|
||||
else if (id === "styleScaleBarBackgroundFillInput") scaleBarBack.attr("fill", value);
|
||||
else if (id === "styleScaleBarBackgroundStrokeInput") scaleBarBack.attr("stroke", value);
|
||||
else if (id === "styleScaleBarBackgroundStrokeWidth") scaleBarBack.attr("stroke-width", value);
|
||||
else if (id === "styleScaleBarBackgroundFilter") scaleBarBack.attr("filter", value);
|
||||
else if (id === "styleScaleBarBackgroundPaddingTop") scaleBarBack.attr("data-top", value);
|
||||
else if (id === "styleScaleBarBackgroundPaddingRight") scaleBarBack.attr("data-right", value);
|
||||
else if (id === "styleScaleBarBackgroundPaddingBottom") scaleBarBack.attr("data-bottom", value);
|
||||
else if (id === "styleScaleBarBackgroundPaddingLeft") scaleBarBack.attr("data-left", value);
|
||||
|
||||
if (
|
||||
[
|
||||
"styleScaleBarSize",
|
||||
"styleScaleBarPositionX",
|
||||
"styleScaleBarPositionY",
|
||||
"styleScaleBarLabel",
|
||||
"styleScaleBarBackgroundPaddingLeft",
|
||||
"styleScaleBarBackgroundPaddingTop",
|
||||
"styleScaleBarBackgroundPaddingRight",
|
||||
"styleScaleBarBackgroundPaddingBottom"
|
||||
].includes(id)
|
||||
) {
|
||||
drawScaleBar(scaleBar, scale);
|
||||
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||
}
|
||||
});
|
||||
|
||||
function updateElements() {
|
||||
// burgIcons to desired size
|
||||
burgIcons.selectAll("g").each(function () {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const systemPresets = [
|
|||
"clean",
|
||||
"atlas",
|
||||
"cyberpunk",
|
||||
"night",
|
||||
"monochrome"
|
||||
];
|
||||
const customPresetPrefix = "fmgStyle_";
|
||||
|
|
@ -61,21 +62,19 @@ async function getStylePreset(desiredPreset) {
|
|||
}
|
||||
|
||||
async function fetchSystemPreset(preset) {
|
||||
const style = await fetch(`./styles/${preset}.json`)
|
||||
.then(res => res.json())
|
||||
.catch(err => {
|
||||
ERROR && console.error("Error on loading style preset", preset, err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!style) throw new Error("Cannot fetch style preset", preset);
|
||||
return style;
|
||||
try {
|
||||
const res = await fetch(`./styles/${preset}.json`);
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
throw new Error("Cannot fetch style preset", preset);
|
||||
}
|
||||
}
|
||||
|
||||
function applyStyle(style) {
|
||||
for (const selector in style) {
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) continue;
|
||||
|
||||
for (const attribute in style[selector]) {
|
||||
const value = style[selector][attribute];
|
||||
|
||||
|
|
@ -90,8 +89,18 @@ function applyStyle(style) {
|
|||
el.setAttribute(attribute, value);
|
||||
}
|
||||
|
||||
if (layerIsOn("toggleTexture") && selector === "#textureImage" && attribute === "src") {
|
||||
el.setAttribute("href", value);
|
||||
if (selector === "#texture") {
|
||||
const image = document.querySelector("#texture > image");
|
||||
if (image) {
|
||||
if (attribute === "data-x") image.setAttribute("x", value);
|
||||
if (attribute === "data-y") image.setAttribute("y", value);
|
||||
if (attribute === "data-href") image.setAttribute("href", value);
|
||||
}
|
||||
}
|
||||
|
||||
// add custom heightmap color scheme
|
||||
if (selector === "#terrs" && attribute === "scheme" && !(value in heightmapColorSchemes)) {
|
||||
addCustomColorScheme(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -99,10 +108,7 @@ function applyStyle(style) {
|
|||
|
||||
function requestStylePresetChange(preset) {
|
||||
const isConfirmed = sessionStorage.getItem("styleChangeConfirmed");
|
||||
if (isConfirmed) {
|
||||
changeStyle(preset);
|
||||
return;
|
||||
}
|
||||
if (isConfirmed) return changeStyle(preset);
|
||||
|
||||
confirmationDialog({
|
||||
title: "Change style preset",
|
||||
|
|
@ -120,8 +126,8 @@ function requestStylePresetChange(preset) {
|
|||
|
||||
async function changeStyle(desiredPreset) {
|
||||
const styleData = await getStylePreset(desiredPreset);
|
||||
const [appliedPreset, style] = styleData;
|
||||
localStorage.setItem("presetStyle", appliedPreset);
|
||||
const [presetName, style] = styleData;
|
||||
localStorage.setItem("presetStyle", presetName);
|
||||
applyStyleWithUiRefresh(style);
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +140,9 @@ function applyStyleWithUiRefresh(style) {
|
|||
|
||||
invokeActiveZooming();
|
||||
setPresetRemoveButtonVisibiliy();
|
||||
|
||||
drawScaleBar(scaleBar, scale);
|
||||
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||
}
|
||||
|
||||
function addStylePreset() {
|
||||
|
|
@ -228,13 +237,23 @@ function addStylePreset() {
|
|||
],
|
||||
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#emblems": ["opacity", "stroke-width", "filter"],
|
||||
"#texture": ["opacity", "filter", "mask"],
|
||||
"#textureImage": ["x", "y", "src"],
|
||||
"#texture": ["opacity", "filter", "mask", "data-x", "data-y", "data-href"],
|
||||
"#zones": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#oceanLayers": ["filter", "layers"],
|
||||
"#oceanBase": ["fill"],
|
||||
"#oceanicPattern": ["href", "opacity"],
|
||||
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
|
||||
"#terrs #oceanHeights": [
|
||||
"data-render",
|
||||
"opacity",
|
||||
"scheme",
|
||||
"terracing",
|
||||
"skip",
|
||||
"relax",
|
||||
"curve",
|
||||
"filter",
|
||||
"mask"
|
||||
],
|
||||
"#terrs #landHeights": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
|
||||
"#legend": [
|
||||
"data-size",
|
||||
"font-size",
|
||||
|
|
@ -294,7 +313,21 @@ function addStylePreset() {
|
|||
"font-family",
|
||||
"filter"
|
||||
],
|
||||
"#fogging": ["opacity", "fill", "filter"]
|
||||
"#fogging": ["opacity", "fill", "filter"],
|
||||
"#vignette": ["opacity", "fill", "filter"],
|
||||
"#vignette-rect": ["x", "y", "width", "height", "rx", "ry", "filter"],
|
||||
"#scaleBar": ["opacity", "fill", "font-size", "data-bar-size", "data-x", "data-y", "data-label"],
|
||||
"#scaleBarBack": [
|
||||
"opacity",
|
||||
"fill",
|
||||
"stroke",
|
||||
"stroke-width",
|
||||
"filter",
|
||||
"data-top",
|
||||
"data-right",
|
||||
"data-bottom",
|
||||
"data-left"
|
||||
]
|
||||
};
|
||||
|
||||
for (const selector in attributes) {
|
||||
|
|
|
|||
|
|
@ -136,7 +136,14 @@ window.UISubmap = (function () {
|
|||
}
|
||||
|
||||
async function loadPreview($container, w, h) {
|
||||
const url = await getMapURL("png", {globe: false, noWater: true, fullMap: true, noLabels: true, noScaleBar: true, noIce: true});
|
||||
const url = await getMapURL("png", {
|
||||
globe: false,
|
||||
noWater: true,
|
||||
fullMap: true,
|
||||
noLabels: true,
|
||||
noScaleBar: true,
|
||||
noIce: true
|
||||
});
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
|
@ -173,7 +180,11 @@ window.UISubmap = (function () {
|
|||
const {angle, shiftX, shiftY, ratio, mirrorH, mirrorV} = getTransformInput();
|
||||
|
||||
const [cx, cy] = [graphWidth / 2, graphHeight / 2];
|
||||
const rot = alfa => (x, y) => [(x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx, (y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy];
|
||||
const rot = alfa => (x, y) =>
|
||||
[
|
||||
(x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx,
|
||||
(y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy
|
||||
];
|
||||
const shift = (dx, dy) => (x, y) => [x + dx, y + dy];
|
||||
const scale = r => (x, y) => [(x - cx) * r + cx, (y - cy) * r + cy];
|
||||
const flipH = (x, y) => [-x + 2 * cx, y];
|
||||
|
|
@ -185,7 +196,11 @@ window.UISubmap = (function () {
|
|||
let inverse = id;
|
||||
|
||||
if (angle) [projection, inverse] = [rot(angle), rot(-angle)];
|
||||
if (ratio) [projection, inverse] = [app(scale(Math.pow(1.1, ratio)), projection), app(inverse, scale(Math.pow(1.1, -ratio)))];
|
||||
if (ratio)
|
||||
[projection, inverse] = [
|
||||
app(scale(Math.pow(1.1, ratio)), projection),
|
||||
app(inverse, scale(Math.pow(1.1, -ratio)))
|
||||
];
|
||||
if (mirrorH) [projection, inverse] = [app(flipH, projection), app(inverse, flipH)];
|
||||
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
|
||||
if (shiftX || shiftY) {
|
||||
|
|
@ -244,7 +259,10 @@ window.UISubmap = (function () {
|
|||
|
||||
// fix scale
|
||||
distanceScaleInput.value = distanceScaleOutput.value = rn((distanceScale = distanceScaleOutput.value / scale), 2);
|
||||
populationRateInput.value = populationRateOutput.value = rn((populationRate = populationRateOutput.value / scale), 2);
|
||||
populationRateInput.value = populationRateOutput.value = rn(
|
||||
(populationRate = populationRateOutput.value / scale),
|
||||
2
|
||||
);
|
||||
customization = 0;
|
||||
startResample(options);
|
||||
}, 1000);
|
||||
|
|
|
|||
|
|
@ -247,13 +247,16 @@ function recreateStates() {
|
|||
capitalsTree.add([x, y]);
|
||||
|
||||
// update label id reference
|
||||
labels
|
||||
.select("#states")
|
||||
.select(`#stateLabel${state.i}`)
|
||||
.attr("id", `stateLabel${newId}`)
|
||||
.select("textPath")
|
||||
.attr("xlink:href", `#textPath_stateLabel${newId}`);
|
||||
defs.select("#textPaths").select(`#textPath_stateLabel${state.i}`).attr("id", `textPath_stateLabel${newId}`);
|
||||
byId(`textPath_stateLabel${state.i}`)?.setAttribute("id", `textPath_stateLabel${newId}`);
|
||||
const $label = byId(`stateLabel${state.i}`);
|
||||
if ($label) {
|
||||
$label.setAttribute("id", `stateLabel${newId}`);
|
||||
const $textPath = $label.querySelector("textPath");
|
||||
if ($textPath) {
|
||||
$textPath.removeAttribute("href");
|
||||
$textPath.setAttribute("href", `#textPath_stateLabel${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// update emblem id reference
|
||||
byId(`stateCOA${state.i}`)?.setAttribute("id", `stateCOA${newId}`);
|
||||
|
|
@ -361,10 +364,10 @@ function regenerateBurgs() {
|
|||
|
||||
const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement
|
||||
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
const existingStatesCount = states.filter(s => s.i && !s.removed).length;
|
||||
const burgsCount =
|
||||
manorsInput.value === "1000"
|
||||
? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) + states.length
|
||||
: +manorsInput.value + states.length;
|
||||
(manorsInput.value === "1000" ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) : +manorsInput.value) +
|
||||
existingStatesCount;
|
||||
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between towns
|
||||
|
||||
for (let i = 0; i < sorted.length && burgs.length < burgsCount; i++) {
|
||||
|
|
|
|||
|
|
@ -11,55 +11,51 @@ function editUnits() {
|
|||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
const drawBar = () => drawScaleBar(scale);
|
||||
const renderScaleBar = () => {
|
||||
drawScaleBar(scaleBar, scale);
|
||||
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||
};
|
||||
|
||||
// add listeners
|
||||
document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit);
|
||||
document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale);
|
||||
document.getElementById("distanceScaleInput").addEventListener("change", changeDistanceScale);
|
||||
document.getElementById("heightUnit").addEventListener("change", changeHeightUnit);
|
||||
document.getElementById("heightExponentInput").addEventListener("input", changeHeightExponent);
|
||||
document.getElementById("heightExponentOutput").addEventListener("input", changeHeightExponent);
|
||||
document.getElementById("temperatureScale").addEventListener("change", changeTemperatureScale);
|
||||
document.getElementById("barSizeOutput").addEventListener("input", drawBar);
|
||||
document.getElementById("barSizeInput").addEventListener("input", drawBar);
|
||||
document.getElementById("barLabel").addEventListener("input", drawBar);
|
||||
document.getElementById("barPosX").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barPosY").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity);
|
||||
document.getElementById("barBackColor").addEventListener("input", changeScaleBarColor);
|
||||
byId("distanceUnitInput").addEventListener("change", changeDistanceUnit);
|
||||
byId("distanceScaleOutput").addEventListener("input", changeDistanceScale);
|
||||
byId("distanceScaleInput").addEventListener("change", changeDistanceScale);
|
||||
byId("heightUnit").addEventListener("change", changeHeightUnit);
|
||||
byId("heightExponentInput").addEventListener("input", changeHeightExponent);
|
||||
byId("heightExponentOutput").addEventListener("input", changeHeightExponent);
|
||||
byId("temperatureScale").addEventListener("change", changeTemperatureScale);
|
||||
|
||||
document.getElementById("populationRateOutput").addEventListener("input", changePopulationRate);
|
||||
document.getElementById("populationRateInput").addEventListener("change", changePopulationRate);
|
||||
document.getElementById("urbanizationOutput").addEventListener("input", changeUrbanizationRate);
|
||||
document.getElementById("urbanizationInput").addEventListener("change", changeUrbanizationRate);
|
||||
document.getElementById("urbanDensityOutput").addEventListener("input", changeUrbanDensity);
|
||||
document.getElementById("urbanDensityInput").addEventListener("change", changeUrbanDensity);
|
||||
byId("populationRateOutput").addEventListener("input", changePopulationRate);
|
||||
byId("populationRateInput").addEventListener("change", changePopulationRate);
|
||||
byId("urbanizationOutput").addEventListener("input", changeUrbanizationRate);
|
||||
byId("urbanizationInput").addEventListener("change", changeUrbanizationRate);
|
||||
byId("urbanDensityOutput").addEventListener("input", changeUrbanDensity);
|
||||
byId("urbanDensityInput").addEventListener("change", changeUrbanDensity);
|
||||
|
||||
document.getElementById("addLinearRuler").addEventListener("click", addRuler);
|
||||
document.getElementById("addOpisometer").addEventListener("click", toggleOpisometerMode);
|
||||
document.getElementById("addRouteOpisometer").addEventListener("click", toggleRouteOpisometerMode);
|
||||
document.getElementById("addPlanimeter").addEventListener("click", togglePlanimeterMode);
|
||||
document.getElementById("removeRulers").addEventListener("click", removeAllRulers);
|
||||
document.getElementById("unitsRestore").addEventListener("click", restoreDefaultUnits);
|
||||
byId("addLinearRuler").addEventListener("click", addRuler);
|
||||
byId("addOpisometer").addEventListener("click", toggleOpisometerMode);
|
||||
byId("addRouteOpisometer").addEventListener("click", toggleRouteOpisometerMode);
|
||||
byId("addPlanimeter").addEventListener("click", togglePlanimeterMode);
|
||||
byId("removeRulers").addEventListener("click", removeAllRulers);
|
||||
byId("unitsRestore").addEventListener("click", restoreDefaultUnits);
|
||||
|
||||
function changeDistanceUnit() {
|
||||
if (this.value === "custom_name") {
|
||||
prompt("Provide a custom name for a distance unit", {default: ""}, custom => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock("distanceUnit");
|
||||
drawScaleBar(scale);
|
||||
renderScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
drawScaleBar(scale);
|
||||
renderScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeDistanceScale() {
|
||||
drawScaleBar(scale);
|
||||
renderScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
|
|
@ -81,14 +77,6 @@ function editUnits() {
|
|||
if (layerIsOn("toggleTemp")) drawTemp();
|
||||
}
|
||||
|
||||
function changeScaleBarOpacity() {
|
||||
scaleBar.select("rect").attr("opacity", this.value);
|
||||
}
|
||||
|
||||
function changeScaleBarColor() {
|
||||
scaleBar.select("rect").attr("fill", this.value);
|
||||
}
|
||||
|
||||
function changePopulationRate() {
|
||||
populationRate = +this.value;
|
||||
}
|
||||
|
|
@ -104,8 +92,8 @@ function editUnits() {
|
|||
function restoreDefaultUnits() {
|
||||
// distanceScale
|
||||
distanceScale = 3;
|
||||
document.getElementById("distanceScaleOutput").value = 3;
|
||||
document.getElementById("distanceScaleInput").value = 3;
|
||||
byId("distanceScaleOutput").value = 3;
|
||||
byId("distanceScaleInput").value = 3;
|
||||
unlock("distanceScale");
|
||||
|
||||
// units
|
||||
|
|
@ -126,20 +114,7 @@ function editUnits() {
|
|||
localStorage.removeItem("heightExponent");
|
||||
calculateTemperatures();
|
||||
|
||||
// scale bar
|
||||
barSizeOutput.value = barSizeInput.value = 2;
|
||||
barLabel.value = "";
|
||||
barBackOpacity.value = 0.2;
|
||||
barBackColor.value = "#ffffff";
|
||||
barPosX.value = barPosY.value = 99;
|
||||
|
||||
localStorage.removeItem("barSize");
|
||||
localStorage.removeItem("barLabel");
|
||||
localStorage.removeItem("barBackOpacity");
|
||||
localStorage.removeItem("barBackColor");
|
||||
localStorage.removeItem("barPosX");
|
||||
localStorage.removeItem("barPosY");
|
||||
drawScaleBar(scale);
|
||||
renderScaleBar();
|
||||
|
||||
// population
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
|
|
@ -152,7 +127,7 @@ function editUnits() {
|
|||
|
||||
function addRuler() {
|
||||
if (!layerIsOn("toggleRulers")) toggleRulers();
|
||||
const pt = document.getElementById("map").createSVGPoint();
|
||||
const pt = byId("map").createSVGPoint();
|
||||
(pt.x = graphWidth / 2), (pt.y = graphHeight / 4);
|
||||
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
|
||||
const dx = graphWidth / 4 / scale;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue