mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-23 20:41:23 +01:00
Merge remote-tracking branch 'upstream/master' into dev-submaps
This commit is contained in:
commit
19f5598187
73 changed files with 7693 additions and 3705 deletions
|
|
@ -1,20 +1,43 @@
|
|||
"use strict";
|
||||
|
||||
window.ThreeD = (function () {
|
||||
// set default options
|
||||
const options = {scale: 50, lightness: 0.7, shadow: 0.5, sun: {x: 100, y: 600, z: 1000}, rotateMesh: 0, rotateGlobe: 0.5, skyColor: "#9ecef5", waterColor: "#466eab", extendedWater: 0, labels3d: 0, resolution: 2};
|
||||
const options = {
|
||||
scale: 50,
|
||||
lightness: 0.7,
|
||||
shadow: 0.5,
|
||||
sun: {x: 100, y: 600, z: 1000},
|
||||
rotateMesh: 0,
|
||||
rotateGlobe: 0.5,
|
||||
skyColor: "#9ecef5",
|
||||
waterColor: "#466eab",
|
||||
extendedWater: 0,
|
||||
labels3d: 0,
|
||||
resolution: 2
|
||||
};
|
||||
|
||||
// set variables
|
||||
let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, raycaster;
|
||||
|
||||
const drawCtx = document.createElement("canvas").getContext("2d");
|
||||
const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
document.body.appendChild(drawSVG);
|
||||
let Renderer,
|
||||
scene,
|
||||
camera,
|
||||
controls,
|
||||
animationFrame,
|
||||
material,
|
||||
texture,
|
||||
geometry,
|
||||
mesh,
|
||||
ambientLight,
|
||||
spotLight,
|
||||
waterPlane,
|
||||
waterMaterial,
|
||||
waterMesh,
|
||||
raycaster;
|
||||
|
||||
let labels = [];
|
||||
let icons = [];
|
||||
let lines = [];
|
||||
|
||||
const context2d = document.createElement("canvas").getContext("2d");
|
||||
|
||||
// initiate 3d scene
|
||||
const create = async function (canvas, type = "viewMesh") {
|
||||
options.isOn = true;
|
||||
|
|
@ -210,16 +233,16 @@ window.ThreeD = (function () {
|
|||
}
|
||||
|
||||
async function createTextLabel({text, font, size, color, quality}) {
|
||||
drawCtx.font = `${size * quality}px ${font}`;
|
||||
drawCtx.canvas.width = drawCtx.measureText(text).width;
|
||||
drawCtx.canvas.height = size * quality * 1.25; // 25% margin as text can overflow the font size
|
||||
drawCtx.clearRect(0, 0, drawCtx.canvas.width, drawCtx.canvas.height);
|
||||
context2d.font = `${size * quality}px ${font}`;
|
||||
context2d.canvas.width = context2d.measureText(text).width;
|
||||
context2d.canvas.height = size * quality * 1.25; // 25% margin as text can overflow the font size
|
||||
context2d.clearRect(0, 0, context2d.canvas.width, context2d.canvas.height);
|
||||
|
||||
drawCtx.font = `${size * quality}px ${font}`;
|
||||
drawCtx.fillStyle = color;
|
||||
drawCtx.fillText(text, 0, size * quality);
|
||||
context2d.font = `${size * quality}px ${font}`;
|
||||
context2d.fillStyle = color;
|
||||
context2d.fillText(text, 0, size * quality);
|
||||
|
||||
return textureToSprite(drawCtx.canvas.toDataURL(), drawCtx.canvas.width / quality, drawCtx.canvas.height / quality);
|
||||
return textureToSprite(context2d.canvas.toDataURL(), context2d.canvas.width / quality, context2d.canvas.height / quality);
|
||||
}
|
||||
|
||||
function get3dCoords(baseX, baseY) {
|
||||
|
|
@ -368,7 +391,8 @@ window.ThreeD = (function () {
|
|||
async function createMesh(width, height, segmentsX, segmentsY) {
|
||||
const mapOptions = {
|
||||
noLabels: options.labels3d,
|
||||
noWater: options.extendedWater
|
||||
noWater: options.extendedWater,
|
||||
fullMap: true
|
||||
};
|
||||
const url = await getMapURL("mesh", mapOptions);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
|
||||
|
|
@ -422,7 +446,8 @@ window.ThreeD = (function () {
|
|||
if (texture) texture.dispose();
|
||||
const mapOptions = {
|
||||
noLabels: options.labels3d,
|
||||
noWater: options.extendedWater
|
||||
noWater: options.extendedWater,
|
||||
fullMap: true
|
||||
};
|
||||
const url = await getMapURL("mesh", mapOptions);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 4000);
|
||||
|
|
@ -503,7 +528,7 @@ window.ThreeD = (function () {
|
|||
material.map = texture;
|
||||
if (addMesh) addGlobe3dMesh();
|
||||
};
|
||||
img2.src = await getMapURL("mesh", {globe: true});
|
||||
img2.src = await getMapURL("mesh", {globe: true, fullMap: true});
|
||||
}
|
||||
|
||||
async function getOBJ() {
|
||||
|
|
@ -578,5 +603,21 @@ window.ThreeD = (function () {
|
|||
});
|
||||
}
|
||||
|
||||
return {create, redraw, update, stop, options, setScale, setLightness, setSun, setRotation, toggleLabels, toggleSky, setResolution, setColors, saveScreenshot, saveOBJ};
|
||||
return {
|
||||
create,
|
||||
redraw,
|
||||
update,
|
||||
stop,
|
||||
options,
|
||||
setScale,
|
||||
setLightness,
|
||||
setSun,
|
||||
setRotation,
|
||||
toggleLabels,
|
||||
toggleSky,
|
||||
setResolution,
|
||||
setColors,
|
||||
saveScreenshot,
|
||||
saveOBJ
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ class Battle {
|
|||
}
|
||||
|
||||
getInitialMorale() {
|
||||
const powerFee = diff => Math.min(Math.max(100 - diff ** 1.5 * 10 + 10, 50), 100);
|
||||
const powerFee = diff => minmax(100 - diff ** 1.5 * 10 + 10, 50, 100);
|
||||
const distanceFee = dist => Math.min(d3.mean(dist) / 50, 15);
|
||||
const powerDiff = this.defenders.power / this.attackers.power;
|
||||
this.attackers.morale = powerFee(powerDiff) - distanceFee(this.attackers.distances);
|
||||
|
|
@ -677,7 +677,22 @@ class Battle {
|
|||
if (note) {
|
||||
const status = side === "attackers" ? battleStatus[0] : battleStatus[1];
|
||||
const losses = r.a ? Math.abs(d3.sum(Object.values(r.casualties))) / r.a : 1;
|
||||
const regStatus = losses === 1 ? "is destroyed" : losses > 0.8 ? "is almost completely destroyed" : losses > 0.5 ? "suffered terrible losses" : losses > 0.3 ? "suffered severe losses" : losses > 0.2 ? "suffered heavy losses" : losses > 0.05 ? "suffered significant losses" : losses > 0 ? "suffered unsignificant losses" : "left the battle without loss";
|
||||
const regStatus =
|
||||
losses === 1
|
||||
? "is destroyed"
|
||||
: losses > 0.8
|
||||
? "is almost completely destroyed"
|
||||
: losses > 0.5
|
||||
? "suffered terrible losses"
|
||||
: losses > 0.3
|
||||
? "suffered severe losses"
|
||||
: losses > 0.2
|
||||
? "suffered heavy losses"
|
||||
: losses > 0.05
|
||||
? "suffered significant losses"
|
||||
: losses > 0
|
||||
? "suffered unsignificant losses"
|
||||
: "left the battle without loss";
|
||||
const casualties = Object.keys(r.casualties)
|
||||
.map(t => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null))
|
||||
.filter(c => c);
|
||||
|
|
@ -691,40 +706,32 @@ class Battle {
|
|||
armies.select(`g#${id} > text`).text(Military.getTotal(r)); // update reg box
|
||||
}
|
||||
|
||||
// append battlefield marker
|
||||
void (function addMarkerSymbol() {
|
||||
if (svg.select("#defs-markers").select("#marker_battlefield").size()) return;
|
||||
const symbol = svg.select("#defs-markers").append("symbol").attr("id", "marker_battlefield").attr("viewBox", "0 0 30 30");
|
||||
symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none");
|
||||
symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1);
|
||||
symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0).attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️");
|
||||
})();
|
||||
const i = last(pack.markers)?.i + 1 || 0;
|
||||
{
|
||||
// append battlefield marker
|
||||
const marker = {i, x: this.x, y: this.y, cell: this.cell, icon: "⚔️", type: "battlefields", dy: 52};
|
||||
pack.markers.push(marker);
|
||||
const markerHTML = drawMarker(marker);
|
||||
document.getElementById("markers").insertAdjacentHTML("beforeend", markerHTML);
|
||||
}
|
||||
|
||||
const getSide = (regs, n) => (regs.length > 1 ? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name);
|
||||
const getSide = (regs, n) =>
|
||||
regs.length > 1
|
||||
? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}`
|
||||
: getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name;
|
||||
const getLosses = casualties => Math.min(rn(casualties * 100), 100);
|
||||
|
||||
const status = battleStatus[+P(0.7)];
|
||||
const result = `The ${this.getTypeName(this.type)} ended in ${status}`;
|
||||
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(this.defenders.regiments, 0)}. ${result}.
|
||||
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(
|
||||
this.defenders.regiments,
|
||||
0
|
||||
)}. ${result}.
|
||||
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`;
|
||||
const id = getNextId("markerElement");
|
||||
notes.push({id, name: this.name, legend});
|
||||
notes.push({id: `marker${i}`, name: this.name, legend});
|
||||
|
||||
tip(`${this.name} is over. ${result}`, true, "success", 4000);
|
||||
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", "#marker_battlefield")
|
||||
.attr("data-id", "#marker_battlefield")
|
||||
.attr("data-x", this.x)
|
||||
.attr("data-y", this.y)
|
||||
.attr("x", this.x - 15)
|
||||
.attr("y", this.y - 30)
|
||||
.attr("data-size", 1)
|
||||
.attr("width", 30)
|
||||
.attr("height", 30);
|
||||
|
||||
$("#battleScreen").dialog("destroy");
|
||||
this.cleanData();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,10 +94,14 @@ function editBiomes() {
|
|||
|
||||
lines += `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability="${b.habitability[i]}"
|
||||
data-cells=${b.cells[i]} data-area=${area} data-population=${population} data-color=${b.color[i]}>
|
||||
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="fillRect pointer"></svg>
|
||||
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
b.color[i]
|
||||
}" class="fillRect pointer"></svg>
|
||||
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Biome habitability percent" class="hide">%</span>
|
||||
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${b.habitability[i]}>
|
||||
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${
|
||||
b.habitability[i]
|
||||
}>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="biomeCells hide">${b.cells[i]}</div>
|
||||
<span data-tip="Biome area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
|
|
@ -189,41 +193,27 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function openWiki(el) {
|
||||
const name = el.parentNode.dataset.name;
|
||||
if (name === "Custom" || !name) {
|
||||
tip("Please provide a biome name", false, "error");
|
||||
return;
|
||||
}
|
||||
const wiki = "https://en.wikipedia.org/wiki/";
|
||||
const biomeName = el.parentNode.dataset.name;
|
||||
if (biomeName === "Custom" || !biomeName) return tip("Please fill in the biome name", false, "error");
|
||||
|
||||
switch (name) {
|
||||
case "Hot desert":
|
||||
openURL(wiki + "Desert_climate#Hot_desert_climates");
|
||||
case "Cold desert":
|
||||
openURL(wiki + "Desert_climate#Cold_desert_climates");
|
||||
case "Savanna":
|
||||
openURL(wiki + "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands");
|
||||
case "Grassland":
|
||||
openURL(wiki + "Temperate_grasslands,_savannas,_and_shrublands");
|
||||
case "Tropical seasonal forest":
|
||||
openURL(wiki + "Seasonal_tropical_forest");
|
||||
case "Temperate deciduous forest":
|
||||
openURL(wiki + "Temperate_deciduous_forest");
|
||||
case "Tropical rainforest":
|
||||
openURL(wiki + "Tropical_rainforest");
|
||||
case "Temperate rainforest":
|
||||
openURL(wiki + "Temperate_rainforest");
|
||||
case "Taiga":
|
||||
openURL(wiki + "Taiga");
|
||||
case "Tundra":
|
||||
openURL(wiki + "Tundra");
|
||||
case "Glacier":
|
||||
openURL(wiki + "Glacier");
|
||||
case "Wetland":
|
||||
openURL(wiki + "Wetland");
|
||||
default:
|
||||
openURL(`https://en.wikipedia.org/w/index.php?search=${name}`);
|
||||
}
|
||||
const wikiBase = "https://en.wikipedia.org/wiki/";
|
||||
const pages = {
|
||||
"Hot desert": "Desert_climate#Hot_desert_climates",
|
||||
"Cold desert": "Desert_climate#Cold_desert_climates",
|
||||
Savanna: "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands",
|
||||
Grassland: "Temperate_grasslands,_savannas,_and_shrublands",
|
||||
"Tropical seasonal forest": "Seasonal_tropical_forest",
|
||||
"Temperate deciduous forest": "Temperate_deciduous_forest",
|
||||
"Tropical rainforest": "Tropical_rainforest",
|
||||
"Temperate rainforest": "Temperate_rainforest",
|
||||
Taiga: "Taiga",
|
||||
Tundra: "Tundra",
|
||||
Glacier: "Glacier",
|
||||
Wetland: "Wetland"
|
||||
};
|
||||
const customBiomeLink = `https://en.wikipedia.org/w/index.php?search=${biomeName}`;
|
||||
const link = pages[biomeName] ? wikiBase + pages[biomeName] : customBiomeLink;
|
||||
openURL(link);
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
|
|
@ -343,7 +333,11 @@ function editBiomes() {
|
|||
$("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
|
||||
tip("Click on biome to select, drag the circle to change biome", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectBiomeOnMapClick).call(d3.drag().on("start", dragBiomeBrush)).on("touchmove mousemove", moveBiomeBrush);
|
||||
viewbox
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectBiomeOnMapClick)
|
||||
.call(d3.drag().on("start", dragBiomeBrush))
|
||||
.on("touchmove mousemove", moveBiomeBrush);
|
||||
}
|
||||
|
||||
function selectBiomeOnLineClick(line) {
|
||||
|
|
|
|||
|
|
@ -10,15 +10,11 @@ function editBurg(id) {
|
|||
burgLabels.selectAll("text").call(d3.drag().on("start", dragBurgLabel)).classed("draggable", true);
|
||||
updateBurgValues();
|
||||
|
||||
const my = id || d3.event.target.tagName === "text" ? "center bottom-20" : "center top+20";
|
||||
const at = id ? "center" : d3.event.target.tagName === "text" ? "top" : "bottom";
|
||||
const of = id ? "svg" : d3.event.target;
|
||||
|
||||
$("#burgEditor").dialog({
|
||||
title: "Edit Burg",
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my, at, of, collision: "fit"}
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
if (modules.editBurg) return;
|
||||
|
|
@ -39,6 +35,8 @@ function editBurg(id) {
|
|||
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
document.getElementById("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("burgStyleShow").addEventListener("click", showStyleSection);
|
||||
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
|
||||
|
|
@ -46,12 +44,12 @@ function editBurg(id) {
|
|||
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
|
||||
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
|
||||
|
||||
document.getElementById("burgSeeInMFCG").addEventListener("click", openInMFCG);
|
||||
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("burgLock").addEventListener("mouseover", showBurgELockTip);
|
||||
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
|
||||
|
||||
function updateBurgValues() {
|
||||
|
|
@ -110,6 +108,14 @@ function editBurg(id) {
|
|||
const coaID = "burgCOA" + id;
|
||||
COArenderer.trigger(coaID, b.coa);
|
||||
document.getElementById("burgEmblem").setAttribute("href", "#" + coaID);
|
||||
|
||||
if (options.showMFCGMap) {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "block";
|
||||
updateMFCGFrame(b);
|
||||
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
|
||||
} else {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
|
||||
|
|
@ -275,12 +281,12 @@ function editBurg(id) {
|
|||
const capital = burgsToRemove.length < burgsInGroup.length;
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to remove
|
||||
${basic || capital ? "all unlocked elements in the group" : "the entire burg group"}?
|
||||
${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}`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove route group",
|
||||
title: "Remove burg group",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
|
|
@ -372,11 +378,6 @@ function editBurg(id) {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgELockTip() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
showBurgLockTip(id);
|
||||
}
|
||||
|
||||
function showStyleSection() {
|
||||
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("burgStyleSection").style.display = "inline-block";
|
||||
|
|
@ -402,59 +403,27 @@ function editBurg(id) {
|
|||
editStyle("anchors", g);
|
||||
}
|
||||
|
||||
function openInMFCG(event) {
|
||||
const id = elSelected.attr("data-id");
|
||||
function updateMFCGFrame(burg) {
|
||||
const mfcgURL = getMFCGlink(burg);
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
}
|
||||
|
||||
function changeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const defSeed = +(seed + id.padStart(4, 0));
|
||||
if (isCtrlClick(event)) {
|
||||
prompt(
|
||||
`Please provide a Medieval Fantasy City Generator seed.
|
||||
Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}).
|
||||
Please note that if seed is custom, "Overworld" button from MFCG will open a different map`,
|
||||
{default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1},
|
||||
v => {
|
||||
burg.MFCG = v;
|
||||
openMFCG(v);
|
||||
}
|
||||
);
|
||||
} else openMFCG();
|
||||
const burgSeed = +this.value;
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
}
|
||||
|
||||
function openMFCG(seed) {
|
||||
if (!seed && burg.MFCGlink) {
|
||||
openURL(burg.MFCGlink);
|
||||
return;
|
||||
}
|
||||
const cells = pack.cells;
|
||||
const name = elSelected.text();
|
||||
const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : "";
|
||||
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;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
}
|
||||
|
||||
const site = "http://fantasycities.watabou.ru/?random=0&continuous=0";
|
||||
const url = `${site}&name=${name}&population=${population}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
openURL(url);
|
||||
}
|
||||
function randomizeSeed() {
|
||||
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 openEmblemEdit() {
|
||||
|
|
@ -463,6 +432,12 @@ 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 toggleRelocateBurg() {
|
||||
const toggler = document.getElementById("toggleCells");
|
||||
document.getElementById("burgRelocate").classList.toggle("pressed");
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ function overviewBurgs() {
|
|||
uploadFile(this, importBurgNames);
|
||||
});
|
||||
document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove);
|
||||
document.getElementById("burgsInvertLock").addEventListener("click", invertLock);
|
||||
|
||||
function refreshBurgsEditor() {
|
||||
updateFilter();
|
||||
|
|
@ -79,20 +80,26 @@ function overviewBurgs() {
|
|||
const province = prov ? pack.provinces[prov].name : "";
|
||||
const culture = pack.cultures[b.culture].name;
|
||||
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${
|
||||
b.name
|
||||
}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled>
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(b.culture)}</select>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
b.culture
|
||||
)}</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)}>
|
||||
<div class="burgType">
|
||||
<span data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}" class="icon-star-empty${b.capital ? "" : " inactive pointer"}"></span>
|
||||
<span data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}" class="icon-star-empty${
|
||||
b.capital ? "" : " inactive pointer"
|
||||
}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}"></span>
|
||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -112,7 +119,6 @@ function overviewBurgs() {
|
|||
body.querySelectorAll("div > span.icon-star-empty").forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("mouseover", showBurgOLockTip));
|
||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerBurgRemove));
|
||||
|
||||
|
|
@ -147,8 +153,8 @@ function overviewBurgs() {
|
|||
function zoomIntoBurg() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
|
||||
const x = +label.getAttribute("x"),
|
||||
y = +label.getAttribute("y");
|
||||
const x = +label.getAttribute("x");
|
||||
const y = +label.getAttribute("y");
|
||||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
|
|
@ -202,11 +208,6 @@ function overviewBurgs() {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgOLockTip() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
showBurgLockTip(burg);
|
||||
}
|
||||
|
||||
function openBurgEditor() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
editBurg(burg);
|
||||
|
|
@ -214,24 +215,15 @@ function overviewBurgs() {
|
|||
|
||||
function triggerBurgRemove() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (pack.burgs[burg].capital) {
|
||||
tip("You cannot remove the capital. Please change the capital first", false, "error");
|
||||
return;
|
||||
}
|
||||
if (pack.burgs[burg].capital) return tip("You cannot remove the capital. Please change the capital first", false, "error");
|
||||
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the burg?";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
confirmationDialog({
|
||||
title: "Remove burg",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
removeBurg(burg);
|
||||
burgsOverviewAddLines();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
message: "Are you sure you want to remove the burg? This actiove cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: () => {
|
||||
removeBurg(burg);
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -239,22 +231,19 @@ function overviewBurgs() {
|
|||
function regenerateNames() {
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
const burg = +el.dataset.id;
|
||||
//if (pack.burgs[burg].lock) return;
|
||||
if (pack.burgs[burg].lock) return;
|
||||
|
||||
const culture = pack.burgs[burg].culture;
|
||||
const name = Names.getCulture(culture);
|
||||
if (!pack.burgs[burg].lock) {
|
||||
el.querySelector(".burgName").value = name;
|
||||
pack.burgs[burg].name = el.dataset.name = name;
|
||||
burgLabels.select("[data-id='" + burg + "']").text(name);
|
||||
}
|
||||
|
||||
el.querySelector(".burgName").value = name;
|
||||
pack.burgs[burg].name = el.dataset.name = name;
|
||||
burgLabels.select("[data-id='" + burg + "']").text(name);
|
||||
});
|
||||
}
|
||||
|
||||
function enterAddBurgMode() {
|
||||
if (this.classList.contains("pressed")) {
|
||||
exitAddBurgMode();
|
||||
return;
|
||||
}
|
||||
if (this.classList.contains("pressed")) return exitAddBurgMode();
|
||||
customization = 3;
|
||||
this.classList.add("pressed");
|
||||
tip("Click on the map to create a new burg. Hold Shift to add multiple", true, "warn");
|
||||
|
|
@ -264,14 +253,9 @@ function overviewBurgs() {
|
|||
function addBurgOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[cell] < 20) {
|
||||
tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||
return;
|
||||
}
|
||||
if (pack.cells.burg[cell]) {
|
||||
tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
||||
return;
|
||||
}
|
||||
if (pack.cells.h[cell] < 20) return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||
if (pack.cells.burg[cell]) return tip("There is already a burg in this cell. Please select a free cell", false, "error");
|
||||
|
||||
addBurg(point); // add new burg
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
|
|
@ -295,6 +279,7 @@ function overviewBurgs() {
|
|||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const burgs = pack.burgs
|
||||
.filter(b => b.i && !b.removed)
|
||||
.map(b => {
|
||||
|
|
@ -306,6 +291,7 @@ function overviewBurgs() {
|
|||
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
|
||||
});
|
||||
const data = states.concat(burgs);
|
||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
|
|
@ -313,8 +299,8 @@ function overviewBurgs() {
|
|||
.sum(d => d.population)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const width = 150 + 200 * uiSizeOutput.value,
|
||||
height = 150 + 200 * uiSizeOutput.value;
|
||||
const width = 150 + 200 * uiSizeOutput.value;
|
||||
const height = 150 + 200 * uiSizeOutput.value;
|
||||
const margin = {top: 0, right: -50, bottom: -10, left: -50};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height - margin.top - margin.bottom;
|
||||
|
|
@ -413,7 +399,14 @@ function overviewBurgs() {
|
|||
if (this.value === "provinces") return d.province;
|
||||
};
|
||||
|
||||
const base = this.value === "states" ? getStatesData() : this.value === "cultures" ? getCulturesData() : this.value === "parent" ? getParentData() : getProvincesData();
|
||||
const mapping = {
|
||||
states: getStatesData,
|
||||
cultures: getCulturesData,
|
||||
parent: getParentData,
|
||||
provinces: getProvincesData
|
||||
};
|
||||
|
||||
const base = mapping[this.value]();
|
||||
burgs.forEach(b => (b.id = b.i + base.length - 1));
|
||||
|
||||
const data = base.concat(burgs);
|
||||
|
|
@ -440,14 +433,15 @@ function overviewBurgs() {
|
|||
width: fitContent(),
|
||||
position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"},
|
||||
buttons: {},
|
||||
close: () => {
|
||||
alertMessage.innerHTML = "";
|
||||
}
|
||||
close: () => (alertMessage.innerHTML = "")
|
||||
});
|
||||
}
|
||||
|
||||
function downloadBurgsData() {
|
||||
let data = "Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Longitude,Latitude,Elevation (" + heightUnit.value + "),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Latitude,Longitude,Elevation (${heightUnit.value}),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town`; // headers
|
||||
if (options.showMFCGMap) data += `,City Generator Link`;
|
||||
data += "\n";
|
||||
|
||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
|
||||
valid.forEach(b => {
|
||||
|
|
@ -463,8 +457,8 @@ function overviewBurgs() {
|
|||
data += rn(b.population * populationRate * urbanization) + ",";
|
||||
|
||||
// add geography data
|
||||
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ",";
|
||||
data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ","; // this is inverted in QGIS otherwise
|
||||
data += getLatitude(b.y, 2) + ",";
|
||||
data += getLongitude(b.x, 2) + ",";
|
||||
data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
|
||||
|
||||
// add status data
|
||||
|
|
@ -474,7 +468,9 @@ function overviewBurgs() {
|
|||
data += b.walls ? "walls," : ",";
|
||||
data += b.plaza ? "plaza," : ",";
|
||||
data += b.temple ? "temple," : ",";
|
||||
data += b.shanty ? "shanty town\n" : "\n";
|
||||
data += b.shanty ? "shanty town," : ",";
|
||||
if (options.showMFCGMap) data += getMFCGlink(b);
|
||||
data += "\n";
|
||||
});
|
||||
|
||||
const name = getFileName("Burgs") + ".csv";
|
||||
|
|
@ -508,19 +504,14 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
function importBurgNames(dataLoaded) {
|
||||
if (!dataLoaded) {
|
||||
tip("Cannot load the file, please check the format", false, "error");
|
||||
return;
|
||||
}
|
||||
if (!dataLoaded) return tip("Cannot load the file, please check the format", false, "error");
|
||||
const data = dataLoaded.split("\r\n");
|
||||
if (!data.length) {
|
||||
tip("Cannot parse the list, please check the file format", false, "error");
|
||||
return;
|
||||
}
|
||||
if (!data.length) return tip("Cannot parse the list, please check the file format", false, "error");
|
||||
|
||||
let change = [],
|
||||
message = `Burgs will be renamed as below. Please confirm`;
|
||||
let change = [];
|
||||
let message = `Burgs to be renamed as below:`;
|
||||
message += `<table class="overflow-table"><tr><th>Id</th><th>Current name</th><th>New Name</th></tr>`;
|
||||
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
for (let i = 0; i < data.length && i <= burgs.length; i++) {
|
||||
const v = data[i];
|
||||
|
|
@ -529,45 +520,36 @@ function overviewBurgs() {
|
|||
message += `<tr><td style="width:20%">${burgs[i].i}</td><td style="width:40%">${burgs[i].name}</td><td style="width:40%">${v}</td></tr>`;
|
||||
}
|
||||
message += `</tr></table>`;
|
||||
|
||||
if (!change.length) message = "No changes found in the file. Please change some names to get a result";
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$("#alert").dialog({
|
||||
title: "Burgs bulk renaming",
|
||||
width: "22em",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Confirm: function () {
|
||||
for (let i = 0; i < change.length; i++) {
|
||||
const id = change[i].id;
|
||||
pack.burgs[id].name = change[i].name;
|
||||
burgLabels.select("[data-id='" + id + "']").text(change[i].name);
|
||||
}
|
||||
$(this).dialog("close");
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
const onConfirm = () => {
|
||||
for (let i = 0; i < change.length; i++) {
|
||||
const id = change[i].id;
|
||||
pack.burgs[id].name = change[i].name;
|
||||
burgLabels.select("[data-id='" + id + "']").text(change[i].name);
|
||||
}
|
||||
burgsOverviewAddLines();
|
||||
};
|
||||
|
||||
confirmationDialog({
|
||||
title: "Burgs bulk renaming",
|
||||
message,
|
||||
confirm: "Rename",
|
||||
onConfirm
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllBurgsRemove() {
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all unlocked burgs except for capitals?
|
||||
<br><i>To remove a capital you have to remove a state first</i>`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove all burgs",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
removeAllBurgs();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
const number = pack.burgs.filter(b => b.i && !b.removed && !b.capital && !b.lock).length;
|
||||
confirmationDialog({
|
||||
title: `Remove ${number} burgs`,
|
||||
message: `
|
||||
Are you sure you want to remove all <i>unlocked</i> burgs except for capitals?
|
||||
<br><i>To remove a capital you have to remove a state first</i>`,
|
||||
confirm: "Remove",
|
||||
onConfirm: removeAllBurgs
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -575,4 +557,9 @@ function overviewBurgs() {
|
|||
pack.burgs.filter(b => b.i && !(b.capital || b.lock)).forEach(b => removeBurg(b.i));
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function invertLock() {
|
||||
pack.burgs = pack.burgs.map(burg => ({...burg, lock: !burg.lock}));
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,9 +62,9 @@ function editCultures() {
|
|||
// add line for each culture
|
||||
function culturesEditorAddLines() {
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
let lines = "",
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
let lines = "";
|
||||
let totalArea = 0;
|
||||
let totalPopulation = 0;
|
||||
|
||||
const emblemShapeGroup = document.getElementById("emblemShape").selectedOptions[0].parentNode.label;
|
||||
const selectShape = emblemShapeGroup === "Diversiform";
|
||||
|
|
@ -84,7 +84,8 @@ function editCultures() {
|
|||
lines += `<div class="states" data-id=${c.i} data-name="${c.name}" data-color="" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Neutral culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span class="icon-cw placeholder"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span class="icon-resize-full placeholder hide"></span>
|
||||
|
|
@ -96,19 +97,28 @@ function editCultures() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ""}
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
</div>`;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
|
||||
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${c.color}" class="fillRect pointer"></svg>
|
||||
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${c.color}" class="fillRect pointer">
|
||||
</svg>
|
||||
<input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span>
|
||||
<input data-tip="Culture expansionism. Defines competitive size. Click to change, then click Recalculate to apply change" class="statePower hide" type="number" min=0 max=99 step=.1 value=${c.expansionism}>
|
||||
<input data-tip="Culture expansionism. Defines competitive size. Click to change, then click Recalculate to apply change" class="statePower hide" type="number" min=0 max=99 step=.1 value=${
|
||||
c.expansionism
|
||||
}>
|
||||
<select data-tip="Culture type. Defines growth model. Click to change" class="cultureType">${getTypeOptions(c.type)}</select>
|
||||
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
|
|
@ -116,7 +126,11 @@ function editCultures() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ""}
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
<span data-tip="Remove culture" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -136,6 +150,7 @@ function editCultures() {
|
|||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectCultureOnLineClick));
|
||||
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", cultureChangeColor));
|
||||
body.querySelectorAll("div > input.cultureName").forEach(el => el.addEventListener("input", cultureChangeName));
|
||||
body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName));
|
||||
body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism));
|
||||
body.querySelectorAll("div > select.cultureType").forEach(el => el.addEventListener("change", cultureChangeType));
|
||||
body.querySelectorAll("div > select.cultureBase").forEach(el => el.addEventListener("change", cultureChangeBase));
|
||||
|
|
@ -258,6 +273,13 @@ function editCultures() {
|
|||
);
|
||||
}
|
||||
|
||||
function cultureRegenerateName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const name = Names.getCultureShort(culture);
|
||||
this.parentNode.querySelector("input.cultureName").value = name;
|
||||
pack.cultures[culture].name = name;
|
||||
}
|
||||
|
||||
function cultureChangeExpansionism() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.expansionism = this.value;
|
||||
|
|
@ -526,7 +548,13 @@ function editCultures() {
|
|||
|
||||
// prepare svg
|
||||
alertMessage.innerHTML = "<div id='cultureInfo' class='chartInfo'>‍</div>";
|
||||
const svg = d3.select("#alertMessage").insert("svg", "#cultureInfo").attr("id", "hierarchy").attr("width", width).attr("height", height).style("text-anchor", "middle");
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#cultureInfo")
|
||||
.attr("id", "hierarchy")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.style("text-anchor", "middle");
|
||||
const graph = svg.append("g").attr("transform", `translate(10, -45)`);
|
||||
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
|
||||
const nodes = graph.append("g");
|
||||
|
|
@ -540,7 +568,24 @@ function editCultures() {
|
|||
.enter()
|
||||
.append("path")
|
||||
.attr("d", d => {
|
||||
return "M" + d.source.x + "," + d.source.y + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 + " " + d.target.x + "," + d.target.y;
|
||||
return (
|
||||
"M" +
|
||||
d.source.x +
|
||||
"," +
|
||||
d.source.y +
|
||||
"C" +
|
||||
d.source.x +
|
||||
"," +
|
||||
(d.source.y * 3 + d.target.y) / 4 +
|
||||
" " +
|
||||
d.target.x +
|
||||
"," +
|
||||
(d.source.y * 2 + d.target.y) / 3 +
|
||||
" " +
|
||||
d.target.x +
|
||||
"," +
|
||||
d.target.y
|
||||
);
|
||||
});
|
||||
|
||||
const node = nodes
|
||||
|
|
@ -661,7 +706,11 @@ function editCultures() {
|
|||
$("#culturesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
|
||||
tip("Click on culture to select, drag the circle to change culture", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectCultureOnMapClick).call(d3.drag().on("start", dragCultureBrush)).on("touchmove mousemove", moveCultureBrush);
|
||||
viewbox
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectCultureOnMapClick)
|
||||
.call(d3.drag().on("start", dragCultureBrush))
|
||||
.on("touchmove mousemove", moveCultureBrush);
|
||||
|
||||
body.querySelector("div").classList.add("selected");
|
||||
}
|
||||
|
|
@ -712,7 +761,14 @@ function editCultures() {
|
|||
|
||||
// change of append new element
|
||||
if (exists.size()) exists.attr("data-culture", cultureNew).attr("fill", color).attr("stroke", color);
|
||||
else temp.append("polygon").attr("data-cell", i).attr("data-culture", cultureNew).attr("points", getPackPolygon(i)).attr("fill", color).attr("stroke", color);
|
||||
else
|
||||
temp
|
||||
.append("polygon")
|
||||
.attr("data-cell", i)
|
||||
.attr("data-culture", cultureNew)
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("fill", color)
|
||||
.attr("stroke", color);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// module stub to store common functions for ui editors
|
||||
"use strict";
|
||||
|
||||
modules.editors = true;
|
||||
restoreDefaultEvents(); // apply default viewbox events on load
|
||||
|
||||
// restore default viewbox events
|
||||
|
|
@ -28,7 +29,7 @@ function clicked() {
|
|||
else if (grand.id === "burgIcons") editBurg();
|
||||
else if (parent.id === "ice") editIce();
|
||||
else if (parent.id === "terrain") editReliefIcon();
|
||||
else if (parent.id === "markers") editMarker();
|
||||
else if (grand.id === "markers" || great.id === "markers") editMarker();
|
||||
else if (grand.id === "coastline") editCoastline();
|
||||
else if (great.id === "armies") editRegiment();
|
||||
else if (pack.cells.t[i] === 1) {
|
||||
|
|
@ -259,20 +260,48 @@ function togglePort(burg) {
|
|||
.attr("height", size);
|
||||
}
|
||||
|
||||
function getBurgSeed(burg) {
|
||||
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
|
||||
}
|
||||
|
||||
function getMFCGlink(burg) {
|
||||
const {cells} = pack;
|
||||
const {name, population, cell} = burg;
|
||||
const burgSeed = getBurgSeed(burg);
|
||||
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
|
||||
const size = minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
const people = rn(population * populationRate * urbanization);
|
||||
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
|
||||
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;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
}
|
||||
|
||||
const baseURL = "https://watabou.github.io/city-generator/?random=0&continuous=0";
|
||||
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
function toggleBurgLock(burg) {
|
||||
const b = pack.burgs[burg];
|
||||
b.lock = b.lock ? 0 : 1;
|
||||
}
|
||||
|
||||
function showBurgLockTip(burg) {
|
||||
const b = pack.burgs[burg];
|
||||
if (b.lock) {
|
||||
tip("Click to Unlock burg and allow it to be change by regeneration tools");
|
||||
} else {
|
||||
tip("Click to Lock burg and prevent changes by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
// draw legend box
|
||||
function drawLegend(name, data) {
|
||||
legend.selectAll("*").remove(); // fully redraw every time
|
||||
|
|
@ -331,7 +360,15 @@ function drawLegend(name, data) {
|
|||
const width = bbox.width + colOffset * 2;
|
||||
const height = bbox.height + colOffset / 2 + vOffset;
|
||||
|
||||
legend.insert("rect", ":first-child").attr("id", "legendBox").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", backClr).attr("fill-opacity", opacity);
|
||||
legend
|
||||
.insert("rect", ":first-child")
|
||||
.attr("id", "legendBox")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("fill", backClr)
|
||||
.attr("fill-opacity", opacity);
|
||||
|
||||
fitLegendBox();
|
||||
}
|
||||
|
|
@ -384,7 +421,15 @@ function createPicker() {
|
|||
const closePicker = () => contaiter.style("display", "none");
|
||||
|
||||
const contaiter = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
|
||||
contaiter.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("opacity", 0.2).on("mousemove", cl).on("click", closePicker);
|
||||
contaiter
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("opacity", 0.2)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
const picker = contaiter
|
||||
.append("g")
|
||||
.attr("id", "picker")
|
||||
|
|
@ -483,9 +528,25 @@ function createPicker() {
|
|||
const width = bbox.width + 8;
|
||||
const height = bbox.height + 9;
|
||||
|
||||
picker.insert("rect", ":first-child").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "#ffffff").attr("stroke", "#5d4651").on("mousemove", pos);
|
||||
picker
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#5d4651")
|
||||
.on("mousemove", pos);
|
||||
picker.insert("text", ":first-child").attr("x", 291).attr("y", -10).attr("id", "pickerCloseText").text("✕");
|
||||
picker.insert("rect", ":first-child").attr("x", 288).attr("y", -21).attr("id", "pickerCloseRect").attr("width", 14).attr("height", 14).on("mousemove", cl).on("click", closePicker);
|
||||
picker
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", 288)
|
||||
.attr("y", -21)
|
||||
.attr("id", "pickerCloseRect")
|
||||
.attr("width", 14)
|
||||
.attr("height", 14)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
picker.insert("text", ":first-child").attr("x", 12).attr("y", -10).attr("id", "pickerLabel").text("Color Picker").on("mousemove", pos);
|
||||
picker.insert("rect", ":first-child").attr("x", 0).attr("y", -30).attr("width", width).attr("height", 30).attr("id", "pickerHeader").on("mousemove", pos);
|
||||
picker.attr("transform", `translate(${(svgWidth - width) / 2},${(svgHeight - height) / 2})`);
|
||||
|
|
@ -695,23 +756,33 @@ function uploadFile(el, callback) {
|
|||
fileReader.onload = loaded => callback(loaded.target.result);
|
||||
}
|
||||
|
||||
function highlightElement(element) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaniosly
|
||||
const box = element.getBBox();
|
||||
function getBBox(element) {
|
||||
const x = +element.getAttribute("x");
|
||||
const y = +element.getAttribute("y");
|
||||
const width = +element.getAttribute("width");
|
||||
const height = +element.getAttribute("height");
|
||||
return {x, y, width, height};
|
||||
}
|
||||
|
||||
function highlightElement(element, zoom) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaneously
|
||||
const box = element.tagName === "svg" ? getBBox(element) : element.getBBox();
|
||||
const transform = element.getAttribute("transform") || null;
|
||||
const enter = d3.transition().duration(1000).ease(d3.easeBounceOut);
|
||||
const exit = d3.transition().duration(500).ease(d3.easeLinear);
|
||||
|
||||
const highlight = debug.append("rect").attr("x", box.x).attr("y", box.y).attr("width", box.width).attr("height", box.height).attr("transform", transform);
|
||||
const highlight = debug.append("rect").attr("x", box.x).attr("y", box.y).attr("width", box.width).attr("height", box.height);
|
||||
highlight.classed("highlighted", 1).attr("transform", transform);
|
||||
highlight.transition(enter).style("outline-offset", "0px").transition(exit).style("outline-color", "transparent").delay(1000).remove();
|
||||
|
||||
highlight.classed("highlighted", 1).transition(enter).style("outline-offset", "0px").transition(exit).style("outline-color", "transparent").delay(1000).remove();
|
||||
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : 3, 1600);
|
||||
if (zoom) {
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : zoom, 1600);
|
||||
}
|
||||
}
|
||||
|
||||
function selectIcon(initial, callback) {
|
||||
|
|
@ -921,6 +992,7 @@ function selectIcon(initial, callback) {
|
|||
}
|
||||
}
|
||||
|
||||
input.oninput = e => callback(input.value);
|
||||
table.onclick = e => {
|
||||
if (e.target.tagName === "TD") {
|
||||
input.value = e.target.innerHTML;
|
||||
|
|
@ -947,6 +1019,37 @@ function selectIcon(initial, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
function confirmationDialog(options) {
|
||||
const {
|
||||
title = "Confirm action",
|
||||
message = "Are you sure you want to continue? <br>The action cannot be reverted",
|
||||
cancel = "Cancel",
|
||||
confirm = "Continue",
|
||||
onCancel,
|
||||
onConfirm
|
||||
} = options;
|
||||
|
||||
const buttons = {
|
||||
[confirm]: function () {
|
||||
if (onConfirm) onConfirm();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
[cancel]: function () {
|
||||
if (onCancel) onCancel();
|
||||
$(this).dialog("close");
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById("alertMessage").innerHTML = message;
|
||||
$("#alert").dialog({resizable: false, title, buttons});
|
||||
}
|
||||
|
||||
// add and register event listeners to clean up on editor closure
|
||||
function listen(element, event, handler) {
|
||||
element.addEventListener(event, handler);
|
||||
return () => element.removeEventListener(event, handler);
|
||||
}
|
||||
|
||||
// Calls the refresh functionality on all editors currently open.
|
||||
function refreshAllEditors() {
|
||||
TIME && console.time("refreshAllEditors");
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
// Module to store general UI functions
|
||||
"use strict";
|
||||
// Module to store general UI functions
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
$(window).resize(function (e) {
|
||||
window.addEventListener("resize", function (e) {
|
||||
if (localStorage.getItem("mapWidth") && localStorage.getItem("mapHeight")) return;
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
changeMapSize();
|
||||
});
|
||||
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
if (location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1") {
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
}
|
||||
|
||||
// Tooltips
|
||||
const tooltip = document.getElementById("tooltip");
|
||||
|
|
@ -19,12 +21,6 @@ document.getElementById("dialogs").addEventListener("mousemove", showDataTip);
|
|||
document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip);
|
||||
document.getElementById("exitCustomization").addEventListener("mousemove", showDataTip);
|
||||
|
||||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
* @param {boolean} main Show above other tooltips
|
||||
* @param {string} type Message type (color): error / warn / success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = "Tip is undefined", main, type, time) {
|
||||
tooltip.innerHTML = tip;
|
||||
tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)";
|
||||
|
|
@ -60,6 +56,15 @@ function showDataTip(e) {
|
|||
tip(dataTip);
|
||||
}
|
||||
|
||||
function showElementLockTip(event) {
|
||||
const locked = event?.target?.classList?.contains("icon-lock");
|
||||
if (locked) {
|
||||
tip("Click to unlock the element and allow it to be changed by regeneration tools");
|
||||
} else {
|
||||
tip("Click to lock the element and prevent changes to it by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
const moved = debounce(mouseMove, 100);
|
||||
function mouseMove() {
|
||||
const point = d3.mouse(this);
|
||||
|
|
@ -84,7 +89,7 @@ function showNotes(e, i) {
|
|||
document.getElementById("notes").style.display = "block";
|
||||
document.getElementById("notesHeader").innerHTML = note.name;
|
||||
document.getElementById("notesBody").innerHTML = note.legend;
|
||||
} else if (!options.pinNotes) {
|
||||
} else if (!options.pinNotes && !markerEditor.offsetParent) {
|
||||
document.getElementById("notes").style.display = "none";
|
||||
document.getElementById("notesHeader").innerHTML = "";
|
||||
document.getElementById("notesBody").innerHTML = "";
|
||||
|
|
@ -105,7 +110,8 @@ function showMapTooltip(point, e, i, g) {
|
|||
|
||||
if (group === "emblems" && e.target.tagName === "use") {
|
||||
const parent = e.target.parentNode;
|
||||
const [g, type] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
const [g, type] =
|
||||
parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
const i = +e.target.dataset.i;
|
||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||
|
||||
|
|
@ -140,7 +146,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
}
|
||||
if (group === "labels") return tip("Click to edit the Label");
|
||||
|
||||
if (group === "markers") return tip("Click to edit the Marker");
|
||||
if (group === "markers") return tip("Click to edit the Marker and pin the marker note");
|
||||
|
||||
if (group === "ruler") {
|
||||
const tag = e.target.tagName;
|
||||
|
|
@ -222,8 +228,8 @@ function updateCellInfo(point, i, g) {
|
|||
const x = (infoX.innerHTML = rn(point[0]));
|
||||
const y = (infoY.innerHTML = rn(point[1]));
|
||||
const f = cells.f[i];
|
||||
infoLat.innerHTML = toDMS(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, "lat");
|
||||
infoLon.innerHTML = toDMS(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, "lon");
|
||||
infoLat.innerHTML = toDMS(getLatitude(y, 4), "lat");
|
||||
infoLon.innerHTML = toDMS(getLongitude(x, 4), "lon");
|
||||
|
||||
infoCell.innerHTML = i;
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
|
|
@ -332,7 +338,20 @@ function highlightEmblemElement(type, el) {
|
|||
|
||||
if (type === "burg") {
|
||||
const {x, y} = el;
|
||||
debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0).attr("fill", "none").attr("stroke", "#d0240f").attr("stroke-width", 1).attr("opacity", 1).transition(animation).attr("r", 20).attr("opacity", 0.1).attr("stroke-width", 0).remove();
|
||||
debug
|
||||
.append("circle")
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("r", 0)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "#d0240f")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.transition(animation)
|
||||
.attr("r", 20)
|
||||
.attr("opacity", 0.1)
|
||||
.attr("stroke-width", 0)
|
||||
.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -481,226 +500,3 @@ function showInfo() {
|
|||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
}
|
||||
|
||||
// prevent default browser behavior for FMG-used hotkeys
|
||||
document.addEventListener("keydown", event => {
|
||||
if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallow alt key combinations
|
||||
if (event.ctrlKey && event.code === "KeyS") event.preventDefault(); // disallow CTRL + C
|
||||
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
|
||||
});
|
||||
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener("keyup", event => {
|
||||
if (!window.closeDialogs) return; // not all modules are loaded
|
||||
const canvas3d = document.getElementById("canvas3d"); // check if 3d mode is active
|
||||
const active = document.activeElement.tagName;
|
||||
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
|
||||
if (active === "DIV" && document.activeElement.contentEditable === "true") return; // don't trigger if user inputs a text
|
||||
event.stopPropagation();
|
||||
|
||||
const key = event.keyCode;
|
||||
const ctrl = event.ctrlKey || event.metaKey || key === 17;
|
||||
const shift = event.shiftKey || key === 16;
|
||||
const alt = event.altKey || key === 18;
|
||||
|
||||
if (key === 112) showInfo();
|
||||
// "F1" to show info
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for new map
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for a new map
|
||||
else if (key === 117) quickSave();
|
||||
// "F6" for quick save
|
||||
else if (key === 120) quickLoad();
|
||||
// "F9" for quick load
|
||||
else if (key === 9) toggleOptions(event);
|
||||
// Tab to toggle options
|
||||
else if (key === 27) {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
} // Escape to close all dialogs
|
||||
else if (key === 46) removeElementOnKey();
|
||||
// "Delete" to remove the selected element
|
||||
else if (key === 79 && canvas3d) toggle3dOptions();
|
||||
// "O" to toggle 3d options
|
||||
else if (ctrl && key === 81) toggleSaveReminder();
|
||||
// Ctrl + "Q" to toggle save reminder
|
||||
else if (ctrl && key === 83) saveMap();
|
||||
// Ctrl + "S" to save .map file
|
||||
else if (undo.offsetParent && ctrl && key === 90) undo.click();
|
||||
// Ctrl + "Z" to undo
|
||||
else if (redo.offsetParent && ctrl && key === 89) redo.click();
|
||||
// Ctrl + "Y" to redo
|
||||
else if (shift && key === 72) editHeightmap();
|
||||
// Shift + "H" to edit Heightmap
|
||||
else if (shift && key === 66) editBiomes();
|
||||
// Shift + "B" to edit Biomes
|
||||
else if (shift && key === 83) editStates();
|
||||
// Shift + "S" to edit States
|
||||
else if (shift && key === 80) editProvinces();
|
||||
// Shift + "P" to edit Provinces
|
||||
else if (shift && key === 68) editDiplomacy();
|
||||
// Shift + "D" to edit Diplomacy
|
||||
else if (shift && key === 67) editCultures();
|
||||
// Shift + "C" to edit Cultures
|
||||
else if (shift && key === 78) editNamesbase();
|
||||
// Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones();
|
||||
// Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions();
|
||||
// Shift + "R" to edit Religions
|
||||
else if (shift && key === 89) openEmblemEditor();
|
||||
// Shift + "Y" to edit Emblems
|
||||
else if (shift && key === 81) editUnits();
|
||||
// Shift + "Q" to edit Units
|
||||
else if (shift && key === 79) editNotes();
|
||||
// Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs();
|
||||
// Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers();
|
||||
// Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 77) overviewMilitary();
|
||||
// Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails();
|
||||
// Shift + "E" to open Cell Details
|
||||
else if (shift && key === 49) toggleAddBurg();
|
||||
// Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel();
|
||||
// Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver();
|
||||
// Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute();
|
||||
// Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker();
|
||||
// Shift + "5" to click to add Marker
|
||||
else if (alt && key === 66) console.table(pack.burgs);
|
||||
// Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states);
|
||||
// Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures);
|
||||
// Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions);
|
||||
// Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features);
|
||||
// Alt + "F" to log features data
|
||||
else if (key === 88) toggleTexture();
|
||||
// "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight();
|
||||
// "H" to toggle Heightmap layer
|
||||
else if (key === 66) toggleBiomes();
|
||||
// "B" to toggle Biomes layer
|
||||
else if (key === 69) toggleCells();
|
||||
// "E" to toggle Cells layer
|
||||
else if (key === 71) toggleGrid();
|
||||
// "G" to toggle Grid layer
|
||||
else if (key === 79) toggleCoordinates();
|
||||
// "O" to toggle Coordinates layer
|
||||
else if (key === 87) toggleCompass();
|
||||
// "W" to toggle Compass Rose layer
|
||||
else if (key === 86) toggleRivers();
|
||||
// "V" to toggle Rivers layer
|
||||
else if (key === 70) toggleRelief();
|
||||
// "F" to toggle Relief icons layer
|
||||
else if (key === 67) toggleCultures();
|
||||
// "C" to toggle Cultures layer
|
||||
else if (key === 83) toggleStates();
|
||||
// "S" to toggle States layer
|
||||
else if (key === 80) toggleProvinces();
|
||||
// "P" to toggle Provinces layer
|
||||
else if (key === 90) toggleZones();
|
||||
// "Z" to toggle Zones
|
||||
else if (key === 68) toggleBorders();
|
||||
// "D" to toggle Borders layer
|
||||
else if (key === 82) toggleReligions();
|
||||
// "R" to toggle Religions layer
|
||||
else if (key === 85) toggleRoutes();
|
||||
// "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp();
|
||||
// "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation();
|
||||
// "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce();
|
||||
// "J" to toggle Ice layer
|
||||
else if (key === 65) togglePrec();
|
||||
// "A" to toggle Precipitation layer
|
||||
else if (key === 89) toggleEmblems();
|
||||
// "Y" to toggle Emblems layer
|
||||
else if (key === 76) toggleLabels();
|
||||
// "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons();
|
||||
// "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMilitary();
|
||||
// "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers();
|
||||
// "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers();
|
||||
// Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar();
|
||||
// Minus (-) to toggle Scale bar
|
||||
else if (key === 37) zoom.translateBy(svg, 10, 0);
|
||||
// Left to scroll map left
|
||||
else if (key === 39) zoom.translateBy(svg, -10, 0);
|
||||
// Right to scroll map right
|
||||
else if (key === 38) zoom.translateBy(svg, 0, 10);
|
||||
// Up to scroll map up
|
||||
else if (key === 40) zoom.translateBy(svg, 0, -10);
|
||||
// Up to scroll map up
|
||||
else if (key === 107 || key === 109) pressNumpadSign(key);
|
||||
// Numpad Plus/Minus to zoom map or change brush size
|
||||
else if (key === 48 || key === 96) resetZoom(1000);
|
||||
// 0 to reset zoom
|
||||
else if (key === 49 || key === 97) zoom.scaleTo(svg, 1);
|
||||
// 1 to zoom to 1
|
||||
else if (key === 50 || key === 98) zoom.scaleTo(svg, 2);
|
||||
// 2 to zoom to 2
|
||||
else if (key === 51 || key === 99) zoom.scaleTo(svg, 3);
|
||||
// 3 to zoom to 3
|
||||
else if (key === 52 || key === 100) zoom.scaleTo(svg, 4);
|
||||
// 4 to zoom to 4
|
||||
else if (key === 53 || key === 101) zoom.scaleTo(svg, 5);
|
||||
// 5 to zoom to 5
|
||||
else if (key === 54 || key === 102) zoom.scaleTo(svg, 6);
|
||||
// 6 to zoom to 6
|
||||
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7);
|
||||
// 7 to zoom to 7
|
||||
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8);
|
||||
// 8 to zoom to 8
|
||||
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9);
|
||||
// 9 to zoom to 9
|
||||
else if (ctrl) pressControl(); // Control to toggle mode
|
||||
});
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
// if brush sliders are displayed, decrease brush size
|
||||
let brush = null;
|
||||
const d = key === 107 ? 1 : -1;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById("brushRadius");
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById("biomesManuallyBrush");
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById("statesManuallyBrush");
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById("provincesManuallyBrush");
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById("culturesManuallyBrush");
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById("zonesBrush");
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById("religionsManuallyBrush");
|
||||
|
||||
if (brush) {
|
||||
const value = Math.max(Math.min(+brush.value + d, +brush.max), +brush.min);
|
||||
brush.value = document.getElementById(brush.id + "Number").value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === 107 ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no, zoom map
|
||||
}
|
||||
|
||||
function pressControl() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains("pressed") ? zonesRemove.classList.remove("pressed") : zonesRemove.classList.add("pressed");
|
||||
}
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$(".dialog:visible .fastDelete").click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,9 @@ function editHeightmap() {
|
|||
title: "Edit Heightmap",
|
||||
width: "28em",
|
||||
buttons: {
|
||||
Erase: function () {
|
||||
enterHeightmapEditMode("erase");
|
||||
},
|
||||
Keep: function () {
|
||||
enterHeightmapEditMode("keep");
|
||||
},
|
||||
Risk: function () {
|
||||
enterHeightmapEditMode("risk");
|
||||
},
|
||||
Erase: () => enterHeightmapEditMode("erase"),
|
||||
Keep: () => enterHeightmapEditMode("keep"),
|
||||
Risk: () => enterHeightmapEditMode("risk"),
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
|
|
@ -87,7 +81,16 @@ function editHeightmap() {
|
|||
exitCustomization.style.bottom = svgHeight / 2 + "px";
|
||||
exitCustomization.style.transform = "scale(2)";
|
||||
exitCustomization.style.display = "block";
|
||||
d3.select("#exitCustomization").transition().duration(1000).style("opacity", 1).transition().duration(2000).ease(d3.easeSinInOut).style("right", "10px").style("bottom", "10px").style("transform", "scale(1)");
|
||||
d3.select("#exitCustomization")
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.style("opacity", 1)
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.ease(d3.easeSinInOut)
|
||||
.style("right", "10px")
|
||||
.style("bottom", "10px")
|
||||
.style("transform", "scale(1)");
|
||||
} else exitCustomization.style.display = "block";
|
||||
|
||||
openBrushesPanel();
|
||||
|
|
@ -130,7 +133,8 @@ function editHeightmap() {
|
|||
|
||||
// Exit customization mode
|
||||
function finalizeHeightmap() {
|
||||
if (viewbox.select("#heights").selectAll("*").size() < 200) return tip("Insufficient land area! There should be at least 200 land cells to finalize the heightmap", null, "error");
|
||||
if (viewbox.select("#heights").selectAll("*").size() < 200)
|
||||
return tip("Insufficient land area! There should be at least 200 land cells to finalize the heightmap", null, "error");
|
||||
if (document.getElementById("imageConverter").offsetParent) return tip("Please exit the Image Conversion mode first", null, "error");
|
||||
|
||||
delete window.edits; // remove global variable
|
||||
|
|
@ -216,7 +220,7 @@ function editHeightmap() {
|
|||
Lakes.generateName();
|
||||
|
||||
Military.generate();
|
||||
addMarkers();
|
||||
Markers.generate();
|
||||
addZones();
|
||||
TIME && console.timeEnd("regenerateErasedData");
|
||||
INFO && console.groupEnd("Edit Heightmap");
|
||||
|
|
@ -608,7 +612,7 @@ function editHeightmap() {
|
|||
const interpolate = d3.interpolateRound(power, 1);
|
||||
const land = changeOnlyLand.checked;
|
||||
function lim(v) {
|
||||
return Math.max(Math.min(v, 100), land ? 20 : 0);
|
||||
return minmax(v, land ? 20 : 0, 100);
|
||||
}
|
||||
const h = grid.cells.h;
|
||||
|
||||
|
|
@ -618,7 +622,10 @@ function editHeightmap() {
|
|||
else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power)));
|
||||
else if (brush === "brushDepress") s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
|
||||
else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start])));
|
||||
else if (brush === "brushSmooth") s.forEach(i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1)));
|
||||
else if (brush === "brushSmooth")
|
||||
s.forEach(
|
||||
i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1))
|
||||
);
|
||||
else if (brush === "brushDisrupt") s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
|
||||
|
||||
mockHeightmapSelection(s);
|
||||
|
|
@ -767,15 +774,29 @@ function editHeightmap() {
|
|||
|
||||
const TempY = `<span>y:<input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${arg5 || "20-80"}></span>`;
|
||||
const TempX = `<span>x:<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || "15-85"}></span>`;
|
||||
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3 || "40-50"}></span>`;
|
||||
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${
|
||||
arg3 || "40-50"
|
||||
}></span>`;
|
||||
const Count = `<span>n:<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || "1-2"}></span>`;
|
||||
const blob = `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
||||
|
||||
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return blob;
|
||||
if (type === "Strait") return `${common}<span>d:<select class="templateDist" data-tip="Strait direction"><option value="vertical" selected>vertical</option><option value="horizontal">horizontal</option></select></span><span>w:<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${count || "2-7"}></span></div>`;
|
||||
if (type === "Add") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)" type="number" value=${count || -10} min=-100 max=100 step=1></span></div>`;
|
||||
if (type === "Multiply") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number" value=${count || 1.1} min=0 max=10 step=.1></span></div>`;
|
||||
if (type === "Smooth") return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${count || 2}></span></div>`;
|
||||
if (type === "Strait")
|
||||
return `${common}<span>d:<select class="templateDist" data-tip="Strait direction"><option value="vertical" selected>vertical</option><option value="horizontal">horizontal</option></select></span><span>w:<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${
|
||||
count || "2-7"
|
||||
}></span></div>`;
|
||||
if (type === "Add")
|
||||
return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)" type="number" value=${
|
||||
count || -10
|
||||
} min=-100 max=100 step=1></span></div>`;
|
||||
if (type === "Multiply")
|
||||
return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number" value=${
|
||||
count || 1.1
|
||||
} min=0 max=10 step=.1></span></div>`;
|
||||
if (type === "Smooth")
|
||||
return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${
|
||||
count || 2
|
||||
}></span></div>`;
|
||||
}
|
||||
|
||||
function setRange(event) {
|
||||
|
|
@ -1170,10 +1191,14 @@ function editHeightmap() {
|
|||
}
|
||||
|
||||
function setConvertColorsNumber() {
|
||||
prompt(`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`, {default: +convertColors.value, step: 1, min: 3, max: 255}, number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
});
|
||||
prompt(
|
||||
`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`,
|
||||
{default: +convertColors.value, step: 1, min: 3, max: 255},
|
||||
number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function setOverlayOpacity(v) {
|
||||
|
|
|
|||
153
modules/ui/hotkeys.js
Normal file
153
modules/ui/hotkeys.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
"use strict";
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener("keydown", handleKeydown);
|
||||
document.addEventListener("keyup", handleKeyup);
|
||||
|
||||
function handleKeydown(event) {
|
||||
const {code, ctrlKey, altKey} = event;
|
||||
if (altKey && !ctrlKey) event.preventDefault(); // disallow alt key combinations
|
||||
if (ctrlKey && ["KeyS", "KeyC"].includes(code)) event.preventDefault(); // disallow CTRL + S and CTRL + C
|
||||
if (["F1", "F2", "F6", "F9", "Tab"].includes(code)) event.preventDefault(); // disallow default Fn and Tab
|
||||
}
|
||||
|
||||
function handleKeyup(event) {
|
||||
if (!modules.editors) return; // if editors are not loaded, do nothing
|
||||
|
||||
const {tagName, contentEditable} = document.activeElement;
|
||||
if (["INPUT", "SELECT", "TEXTAREA"].includes(tagName)) return; // don't trigger if user inputs text
|
||||
if (tagName === "DIV" && contentEditable === "true") return; // don't trigger if user inputs a text
|
||||
if (document.getSelection().toString()) return; // don't trigger if user selects text
|
||||
event.stopPropagation();
|
||||
|
||||
const {code, key, ctrlKey, metaKey, shiftKey, altKey} = event;
|
||||
const ctrl = ctrlKey || metaKey || key === "Control";
|
||||
const shift = shiftKey || key === "Shift";
|
||||
const alt = altKey || key === "Alt";
|
||||
|
||||
if (code === "F1") showInfo();
|
||||
else if (code === "F2") regeneratePrompt("hotkey");
|
||||
else if (code === "F6") quickSave();
|
||||
else if (code === "F9") quickLoad();
|
||||
else if (code === "Tab") toggleOptions(event);
|
||||
else if (code === "Escape") closeAllDialogs();
|
||||
else if (code === "Delete") removeElementOnKey();
|
||||
else if (code === "KeyO" && document.getElementById("canvas3d")) toggle3dOptions();
|
||||
else if (ctrl && code === "KeyQ") toggleSaveReminder();
|
||||
else if (ctrl && code === "KeyS") dowloadMap();
|
||||
else if (ctrl && code === "KeyC") saveToDropbox();
|
||||
else if (ctrl && code === "KeyZ" && undo.offsetParent) undo.click();
|
||||
else if (ctrl && code === "KeyY" && redo.offsetParent) redo.click();
|
||||
else if (shift && code === "KeyH") editHeightmap();
|
||||
else if (shift && code === "KeyB") editBiomes();
|
||||
else if (shift && code === "KeyS") editStates();
|
||||
else if (shift && code === "KeyP") editProvinces();
|
||||
else if (shift && code === "KeyD") editDiplomacy();
|
||||
else if (shift && code === "KeyC") editCultures();
|
||||
else if (shift && code === "KeyN") editNamesbase();
|
||||
else if (shift && code === "KeyZ") editZones();
|
||||
else if (shift && code === "KeyR") editReligions();
|
||||
else if (shift && code === "KeyY") openEmblemEditor();
|
||||
else if (shift && code === "KeyQ") editUnits();
|
||||
else if (shift && code === "KeyO") editNotes();
|
||||
else if (shift && code === "KeyT") overviewBurgs();
|
||||
else if (shift && code === "KeyV") overviewRivers();
|
||||
else if (shift && code === "KeyM") overviewMilitary();
|
||||
else if (shift && code === "KeyK") overviewMarkers();
|
||||
else if (shift && code === "KeyE") viewCellDetails();
|
||||
else if (key === "!") toggleAddBurg();
|
||||
else if (key === "@") toggleAddLabel();
|
||||
else if (key === "#") toggleAddRiver();
|
||||
else if (key === "$") toggleAddRoute();
|
||||
else if (key === "%") toggleAddMarker();
|
||||
else if (alt && code === "KeyB") console.table(pack.burgs);
|
||||
else if (alt && code === "KeyS") console.table(pack.states);
|
||||
else if (alt && code === "KeyC") console.table(pack.cultures);
|
||||
else if (alt && code === "KeyR") console.table(pack.religions);
|
||||
else if (alt && code === "KeyF") console.table(pack.features);
|
||||
else if (code === "KeyX") toggleTexture();
|
||||
else if (code === "KeyH") toggleHeight();
|
||||
else if (code === "KeyB") toggleBiomes();
|
||||
else if (code === "KeyE") toggleCells();
|
||||
else if (code === "KeyG") toggleGrid();
|
||||
else if (code === "KeyO") toggleCoordinates();
|
||||
else if (code === "KeyW") toggleCompass();
|
||||
else if (code === "KeyV") toggleRivers();
|
||||
else if (code === "KeyF") toggleRelief();
|
||||
else if (code === "KeyC") toggleCultures();
|
||||
else if (code === "KeyS") toggleStates();
|
||||
else if (code === "KeyP") toggleProvinces();
|
||||
else if (code === "KeyZ") toggleZones();
|
||||
else if (code === "KeyD") toggleBorders();
|
||||
else if (code === "KeyR") toggleReligions();
|
||||
else if (code === "KeyU") toggleRoutes();
|
||||
else if (code === "KeyT") toggleTemp();
|
||||
else if (code === "KeyN") togglePopulation();
|
||||
else if (code === "KeyJ") toggleIce();
|
||||
else if (code === "KeyA") togglePrec();
|
||||
else if (code === "KeyY") toggleEmblems();
|
||||
else if (code === "KeyL") toggleLabels();
|
||||
else if (code === "KeyI") toggleIcons();
|
||||
else if (code === "KeyM") toggleMilitary();
|
||||
else if (code === "KeyK") toggleMarkers();
|
||||
else if (code === "Equal") toggleRulers();
|
||||
else if (code === "Slash") toggleScaleBar();
|
||||
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);
|
||||
else if (code === "ArrowDown") zoom.translateBy(svg, 0, -10);
|
||||
else if (key === "+" || key === "-") pressNumpadSign(key);
|
||||
else if (key === "0") resetZoom(1000);
|
||||
else if (key === "1") zoom.scaleTo(svg, 1);
|
||||
else if (key === "2") zoom.scaleTo(svg, 2);
|
||||
else if (key === "3") zoom.scaleTo(svg, 3);
|
||||
else if (key === "4") zoom.scaleTo(svg, 4);
|
||||
else if (key === "5") zoom.scaleTo(svg, 5);
|
||||
else if (key === "6") zoom.scaleTo(svg, 6);
|
||||
else if (key === "7") zoom.scaleTo(svg, 7);
|
||||
else if (key === "8") zoom.scaleTo(svg, 8);
|
||||
else if (key === "9") zoom.scaleTo(svg, 9);
|
||||
else if (ctrl) toggleMode();
|
||||
}
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
const change = key === "+" ? 1 : -1;
|
||||
let brush = null;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById("brushRadius");
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById("biomesManuallyBrush");
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById("statesManuallyBrush");
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById("provincesManuallyBrush");
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById("culturesManuallyBrush");
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById("zonesBrush");
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById("religionsManuallyBrush");
|
||||
|
||||
if (brush) {
|
||||
const value = minmax(+brush.value + change, +brush.min, +brush.max);
|
||||
brush.value = document.getElementById(brush.id + "Number").value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === "+" ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no brush elements displayed, zoom map
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains("pressed") ? zonesRemove.classList.remove("pressed") : zonesRemove.classList.add("pressed");
|
||||
}
|
||||
}
|
||||
|
||||
function removeElementOnKey() {
|
||||
const fastDelete = Array.from(document.querySelectorAll("[role='dialog'] .fastDelete")).find(dialog => dialog.style.display !== "none");
|
||||
if (fastDelete) fastDelete.click();
|
||||
|
||||
const visibleDialogs = Array.from(document.querySelectorAll("[role='dialog']")).filter(dialog => dialog.style.display !== "none");
|
||||
if (!visibleDialogs.length) return;
|
||||
|
||||
visibleDialogs.forEach(dialog => dialog.querySelectorAll("button").forEach(button => button.textContent === "Remove" && button.click()));
|
||||
}
|
||||
|
||||
function closeAllDialogs() {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
}
|
||||
|
|
@ -11,7 +11,9 @@ function editLabel() {
|
|||
viewbox.on("touchmove mousemove", showEditorTips);
|
||||
|
||||
$("#labelEditor").dialog({
|
||||
title: "Edit Label", resizable: false, width: fitContent(),
|
||||
title: "Edit Label",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: "center top+10", at: "bottom", of: text, collision: "fit"},
|
||||
close: closeLabelEditor
|
||||
});
|
||||
|
|
@ -49,8 +51,8 @@ function editLabel() {
|
|||
|
||||
function showEditorTips() {
|
||||
showMainTip();
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr("id")) tip("Drag to shift the label"); else
|
||||
if (d3.event.target.parentNode.id === "controlPoints") {
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr("id")) tip("Drag to shift the label");
|
||||
else if (d3.event.target.parentNode.id === "controlPoints") {
|
||||
if (d3.event.target.tagName === "circle") tip("Drag to move, click to delete the control point");
|
||||
if (d3.event.target.tagName === "path") tip("Click to add a control point");
|
||||
}
|
||||
|
|
@ -58,10 +60,18 @@ function editLabel() {
|
|||
|
||||
function selectLabelGroup(text) {
|
||||
const group = text.parentNode.id;
|
||||
|
||||
if (group === "states" || group === "burgLabels") {
|
||||
document.getElementById("labelGroupShow").style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
hideGroupSection();
|
||||
const select = document.getElementById("labelGroupSelect");
|
||||
select.options.length = 0; // remove all options
|
||||
|
||||
labels.selectAll(":scope > g").each(function() {
|
||||
labels.selectAll(":scope > g").each(function () {
|
||||
if (this.id === "states") return;
|
||||
if (this.id === "burgLabels") return;
|
||||
select.options.add(new Option(this.id, this.id, false, this.id === group));
|
||||
});
|
||||
|
|
@ -81,12 +91,19 @@ function editLabel() {
|
|||
const l = path.getTotalLength();
|
||||
if (!l) return;
|
||||
const increment = l / Math.max(Math.ceil(l / 200), 2);
|
||||
for (let i=0; i <= l; i += increment) {addControlPoint(path.getPointAtLength(i));}
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
addControlPoint(path.getPointAtLength(i));
|
||||
}
|
||||
}
|
||||
|
||||
function addControlPoint(point) {
|
||||
debug.select("#controlPoints").append("circle")
|
||||
.attr("cx", point.x).attr("cy", point.y).attr("r", 2.5).attr("stroke-width", .8)
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.append("circle")
|
||||
.attr("cx", point.x)
|
||||
.attr("cy", point.y)
|
||||
.attr("r", 2.5)
|
||||
.attr("stroke-width", 0.8)
|
||||
.call(d3.drag().on("drag", dragControlPoint))
|
||||
.on("click", clickControlPoint);
|
||||
}
|
||||
|
|
@ -101,9 +118,12 @@ function editLabel() {
|
|||
const path = document.getElementById("textPath_" + elSelected.attr("id"));
|
||||
lineGen.curve(d3.curveBundle.beta(1));
|
||||
const points = [];
|
||||
debug.select("#controlPoints").selectAll("circle").each(function() {
|
||||
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
|
||||
});
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
|
||||
});
|
||||
const d = round(lineGen(points));
|
||||
path.setAttribute("d", d);
|
||||
debug.select("#controlPoints > path").attr("d", d);
|
||||
|
|
@ -118,52 +138,63 @@ function editLabel() {
|
|||
const point = d3.mouse(this);
|
||||
|
||||
const dists = [];
|
||||
debug.select("#controlPoints").selectAll("circle").each(function() {
|
||||
const x = +this.getAttribute("cx");
|
||||
const y = +this.getAttribute("cy");
|
||||
dists.push((point[0] - x) ** 2 + (point[1] - y) ** 2);
|
||||
});
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
const x = +this.getAttribute("cx");
|
||||
const y = +this.getAttribute("cy");
|
||||
dists.push((point[0] - x) ** 2 + (point[1] - y) ** 2);
|
||||
});
|
||||
|
||||
let index = dists.length;
|
||||
if (dists.length > 1) {
|
||||
const sorted = dists.slice(0).sort((a, b) => a-b);
|
||||
const sorted = dists.slice(0).sort((a, b) => a - b);
|
||||
const closest = dists.indexOf(sorted[0]);
|
||||
const next = dists.indexOf(sorted[1]);
|
||||
if (closest <= next) index = closest+1; else index = next+1;
|
||||
if (closest <= next) index = closest + 1;
|
||||
else index = next + 1;
|
||||
}
|
||||
|
||||
const before = ":nth-child(" + (index + 2) + ")";
|
||||
debug.select("#controlPoints").insert("circle", before)
|
||||
.attr("cx", point[0]).attr("cy", point[1]).attr("r", 2.5).attr("stroke-width", .8)
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.insert("circle", before)
|
||||
.attr("cx", point[0])
|
||||
.attr("cy", point[1])
|
||||
.attr("r", 2.5)
|
||||
.attr("stroke-width", 0.8)
|
||||
.call(d3.drag().on("drag", dragControlPoint))
|
||||
.on("click", clickControlPoint);
|
||||
|
||||
redrawLabelPath();
|
||||
redrawLabelPath();
|
||||
}
|
||||
|
||||
function dragLabel() {
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
const dx = +tr[0] - d3.event.x, dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const x = d3.event.x, y = d3.event.y;
|
||||
const transform = `translate(${(dx+x)},${(dy+y)})`;
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
const x = d3.event.x,
|
||||
y = d3.event.y;
|
||||
const transform = `translate(${dx + x},${dy + y})`;
|
||||
elSelected.attr("transform", transform);
|
||||
debug.select("#controlPoints").attr("transform", transform);
|
||||
});
|
||||
}
|
||||
|
||||
function showGroupSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none");
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelGroupSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "inline-block");
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelGroupSection").style.display = "none";
|
||||
document.getElementById("labelGroupInput").style.display = "none";
|
||||
document.getElementById("labelGroupInput").value = "";
|
||||
document.getElementById("labelGroupSelect").style.display = "inline-block";
|
||||
document.getElementById("labelGroupSelect").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
|
|
@ -178,12 +209,18 @@ function editLabel() {
|
|||
} else {
|
||||
labelGroupInput.style.display = "none";
|
||||
labelGroupSelect.style.display = "inline-block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {tip("Please provide a valid group name"); return;}
|
||||
const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
||||
if (!this.value) {
|
||||
tip("Please provide a valid group name");
|
||||
return;
|
||||
}
|
||||
const group = this.value
|
||||
.toLowerCase()
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (document.getElementById(group)) {
|
||||
tip("Element with this id already exists. Please provide a unique name", false, "error");
|
||||
|
|
@ -223,57 +260,64 @@ function editLabel() {
|
|||
alertMessage.innerHTML = `Are you sure you want to remove
|
||||
${basic ? "all elements in the group" : "the entire label group"}?
|
||||
<br><br>Labels to be removed: ${count}`;
|
||||
$("#alert").dialog({resizable: false, title: "Remove route group",
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove route group",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
$("#labelEditor").dialog("close");
|
||||
hideGroupSection();
|
||||
labels.select("#"+group).selectAll("text").each(function() {
|
||||
document.getElementById("textPath_" + this.id).remove();
|
||||
this.remove();
|
||||
});
|
||||
if (!basic) labels.select("#"+group).remove();
|
||||
labels
|
||||
.select("#" + group)
|
||||
.selectAll("text")
|
||||
.each(function () {
|
||||
document.getElementById("textPath_" + this.id).remove();
|
||||
this.remove();
|
||||
});
|
||||
if (!basic) labels.select("#" + group).remove();
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function showTextSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none");
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelTextSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideTextSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "inline-block");
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelTextSection").style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
function changeText() {
|
||||
const input = document.getElementById("labelText").value;
|
||||
const el = elSelected.select("textPath").node();
|
||||
const example = d3.select(elSelected.node().parentNode)
|
||||
.append("text").attr("x", 0).attr("x", 0)
|
||||
.attr("font-size", el.getAttribute("font-size")).node();
|
||||
const example = d3.select(elSelected.node().parentNode).append("text").attr("x", 0).attr("x", 0).attr("font-size", el.getAttribute("font-size")).node();
|
||||
|
||||
const lines = input.split("|");
|
||||
const top = (lines.length - 1) / -2; // y offset
|
||||
const inner = lines.map((l, d) => {
|
||||
example.innerHTML = l;
|
||||
const left = example.getBBox().width / -2; // x offset
|
||||
return `<tspan x="${left}px" dy="${d?1:top}em">${l}</tspan>`;
|
||||
}).join("");
|
||||
const inner = lines
|
||||
.map((l, d) => {
|
||||
example.innerHTML = l;
|
||||
const left = example.getBBox().width / -2; // x offset
|
||||
return `<tspan x="${left}px" dy="${d ? 1 : top}em">${l}</tspan>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
el.innerHTML = inner;
|
||||
example.remove();
|
||||
|
||||
if (elSelected.attr("id").slice(0,10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning");
|
||||
if (elSelected.attr("id").slice(0, 10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning");
|
||||
}
|
||||
|
||||
function generateRandomName() {
|
||||
let name = "";
|
||||
if (elSelected.attr("id").slice(0,10) === "stateLabel") {
|
||||
if (elSelected.attr("id").slice(0, 10) === "stateLabel") {
|
||||
const id = +elSelected.attr("id").slice(10);
|
||||
const culture = pack.states[id].culture;
|
||||
name = Names.getState(Names.getCulture(culture, 4, 7, ""), culture);
|
||||
|
|
@ -293,12 +337,12 @@ function editLabel() {
|
|||
}
|
||||
|
||||
function showSizeSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none");
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelSizeSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideSizeSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "inline-block");
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelSizeSection").style.display = "none";
|
||||
}
|
||||
|
||||
|
|
@ -317,7 +361,7 @@ function editLabel() {
|
|||
const bbox = elSelected.node().getBBox();
|
||||
const c = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
|
||||
const path = defs.select("#textPath_" + elSelected.attr("id"));
|
||||
path.attr("d", `M${c[0]-bbox.width},${c[1]}h${bbox.width*2}`);
|
||||
path.attr("d", `M${c[0] - bbox.width},${c[1]}h${bbox.width * 2}`);
|
||||
drawControlPointsAndLine();
|
||||
}
|
||||
|
||||
|
|
@ -329,15 +373,19 @@ function editLabel() {
|
|||
|
||||
function removeLabel() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the label?";
|
||||
$("#alert").dialog({resizable: false, title: "Remove label",
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove label",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
defs.select("#textPath_" + elSelected.attr("id")).remove();
|
||||
elSelected.remove();
|
||||
$("#labelEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@ function changePreset(preset) {
|
|||
.querySelectorAll("li")
|
||||
.forEach(function (e) {
|
||||
if (layers.includes(e.id) && !layerIsOn(e.id)) e.click();
|
||||
// turn on
|
||||
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off
|
||||
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click();
|
||||
});
|
||||
layersPreset.value = preset;
|
||||
localStorage.setItem("preset", preset);
|
||||
|
|
@ -121,6 +120,7 @@ function restoreLayers() {
|
|||
if (layerIsOn("toggleReligions")) drawReligions();
|
||||
if (layerIsOn("toggleIce")) drawIce();
|
||||
if (layerIsOn("toggleEmblems")) drawEmblems();
|
||||
if (layerIsOn("toggleMarkers")) drawMarkers();
|
||||
|
||||
// some layers are rendered each time, remove them if they are not on
|
||||
if (!layerIsOn("toggleBorders")) borders.selectAll("path").remove();
|
||||
|
|
@ -196,7 +196,8 @@ function drawHeightmap() {
|
|||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -798,7 +799,10 @@ function drawReligions() {
|
|||
if (!vArray[r]) vArray[r] = [];
|
||||
vArray[r].push(points);
|
||||
body[r] += "M" + points.join("L");
|
||||
gap[r] += "M" + vertices.p[chain[0][0]] + chain.reduce((r2, v, i, d) => (!i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r2 + "M" + vertices.p[v[0]] : r2), "");
|
||||
gap[r] +=
|
||||
"M" +
|
||||
vertices.p[chain[0][0]] +
|
||||
chain.reduce((r2, v, i, d) => (!i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r2 + "M" + vertices.p[v[0]] : r2), "");
|
||||
}
|
||||
|
||||
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
||||
|
|
@ -965,7 +969,14 @@ function drawStates() {
|
|||
const bodyString = bodyData.map(d => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join("");
|
||||
const gapString = gapData.map(d => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join("");
|
||||
const clipString = bodyData.map(d => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`).join("");
|
||||
const haloString = haloData.map(d => `<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"}"/>`).join("");
|
||||
const haloString = haloData
|
||||
.map(
|
||||
d =>
|
||||
`<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${
|
||||
d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"
|
||||
}"/>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
statesBody.html(bodyString + gapString);
|
||||
defs.select("#statePaths").html(clipString);
|
||||
|
|
@ -1217,7 +1228,10 @@ function getProvincesVertices() {
|
|||
if (!vArray[p]) vArray[p] = [];
|
||||
vArray[p].push(points);
|
||||
body[p] += "M" + points.join("L");
|
||||
gap[p] += "M" + vertices.p[chain[0][0]] + chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r), "");
|
||||
gap[p] +=
|
||||
"M" +
|
||||
vertices.p[chain[0][0]] +
|
||||
chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r), "");
|
||||
}
|
||||
|
||||
// find province visual center
|
||||
|
|
@ -1298,7 +1312,12 @@ function drawGrid() {
|
|||
const maxWidth = Math.max(+mapWidthInput.value, graphWidth);
|
||||
const maxHeight = Math.max(+mapHeightInput.value, graphHeight);
|
||||
|
||||
d3.select(pattern).attr("stroke", stroke).attr("stroke-width", width).attr("stroke-dasharray", dasharray).attr("stroke-linecap", linecap).attr("patternTransform", tr);
|
||||
d3.select(pattern)
|
||||
.attr("stroke", stroke)
|
||||
.attr("stroke-width", width)
|
||||
.attr("stroke-dasharray", dasharray)
|
||||
.attr("stroke-linecap", linecap)
|
||||
.attr("patternTransform", tr);
|
||||
gridOverlay
|
||||
.append("rect")
|
||||
.attr("width", maxWidth)
|
||||
|
|
@ -1416,8 +1435,8 @@ function toggleTexture(event) {
|
|||
turnButtonOn("toggleTexture");
|
||||
// append default texture image selected by default. Don't append on load to not harm performance
|
||||
if (!texture.selectAll("*").size()) {
|
||||
const x = +styleTextureShiftX.value,
|
||||
y = +styleTextureShiftY.value;
|
||||
const x = +styleTextureShiftX.value;
|
||||
const y = +styleTextureShiftY.value;
|
||||
const image = texture
|
||||
.append("image")
|
||||
.attr("id", "textureImage")
|
||||
|
|
@ -1425,18 +1444,14 @@ function toggleTexture(event) {
|
|||
.attr("y", y)
|
||||
.attr("width", graphWidth - x)
|
||||
.attr("height", graphHeight - y)
|
||||
.attr("xlink:href", getDefaultTexture())
|
||||
.attr("preserveAspectRatio", "xMidYMid slice");
|
||||
if (styleTextureInput.value !== "default") getBase64(styleTextureInput.value, base64 => image.attr("xlink:href", base64));
|
||||
getBase64(styleTextureInput.value, base64 => image.attr("xlink:href", base64));
|
||||
}
|
||||
$("#texture").fadeIn();
|
||||
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
|
||||
if (event && isCtrlClick(event)) editStyle("texture");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("texture");
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle("texture");
|
||||
$("#texture").fadeOut();
|
||||
turnButtonOff("toggleTexture");
|
||||
}
|
||||
|
|
@ -1505,18 +1520,53 @@ function toggleMilitary() {
|
|||
function toggleMarkers(event) {
|
||||
if (!layerIsOn("toggleMarkers")) {
|
||||
turnButtonOn("toggleMarkers");
|
||||
$("#markers").fadeIn();
|
||||
drawMarkers();
|
||||
if (event && isCtrlClick(event)) editStyle("markers");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("markers");
|
||||
return;
|
||||
}
|
||||
$("#markers").fadeOut();
|
||||
if (event && isCtrlClick(event)) return editStyle("markers");
|
||||
markers.selectAll("*").remove();
|
||||
turnButtonOff("toggleMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
function drawMarkers() {
|
||||
const rescale = +markers.attr("rescale");
|
||||
const pinned = +markers.attr("pinned");
|
||||
|
||||
const markersData = pinned ? pack.markers.filter(({pinned}) => pinned) : pack.markers;
|
||||
const html = markersData.map(marker => drawMarker(marker, rescale));
|
||||
markers.html(html.join(""));
|
||||
}
|
||||
|
||||
const getPin = (shape = "bubble", fill = "#fff", stroke = "#000") => {
|
||||
if (shape === "bubble")
|
||||
return `<path d="M6,19 l9,10 L24,19" fill="${stroke}" stroke="none" /><circle cx="15" cy="15" r="10" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === "pin")
|
||||
return `<path d="m 15,3 c -5.5,0 -9.7,4.09 -9.7,9.3 0,6.8 9.7,17 9.7,17 0,0 9.7,-10.2 9.7,-17 C 24.7,7.09 20.5,3 15,3 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === "square") return `<path d="m 20,25 -5,4 -5,-4 z" fill="${stroke}"/><path d="M 5,5 H 25 V 25 H 5 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === "squarish") return `<path d="m 5,5 h 20 v 20 h -6 l -4,4 -4,-4 H 5 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "diamond") return `<path d="M 2,15 15,1 28,15 15,29 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "hex") return `<path d="M 15,29 4.61,21 V 9 L 15,3 25.4,9 v 12 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "hexy") return `<path d="M 15,29 6,21 5,8 15,4 25,8 24,21 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "shieldy") return `<path d="M 15,29 6,21 5,7 c 0,0 5,-3 10,-3 5,0 10,3 10,3 l -1,14 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "shield") return `<path d="M 4.6,5.2 H 25 v 6.7 A 20.3,20.4 0 0 1 15,29 20.3,20.4 0 0 1 4.6,11.9 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "pentagon") return `<path d="M 4,16 9,4 h 12 l 5,12 -11,13 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "heptagon") return `<path d="M 15,29 6,22 4,12 10,4 h 10 l 6,8 -2,10 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "circle") return `<circle cx="15" cy="15" r="11" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "no") return "";
|
||||
};
|
||||
|
||||
function drawMarker(marker, rescale = 1) {
|
||||
const {i, icon, x, y, dx = 50, dy = 50, px = 12, size = 30, pin, fill, stroke} = marker;
|
||||
const id = `marker${i}`;
|
||||
const zoomSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
const viewX = rn(x - zoomSize / 2, 1);
|
||||
const viewY = rn(y - zoomSize, 1);
|
||||
const pinHTML = getPin(pin, fill, stroke);
|
||||
|
||||
return `<svg id="${id}" viewbox="0 0 30 30" width="${zoomSize}" height="${zoomSize}" x="${viewX}" y="${viewY}"><g>${pinHTML}</g><text x="${dx}%" y="${dy}%" font-size="${px}px" >${icon}</text></svg>`;
|
||||
}
|
||||
|
||||
function toggleLabels(event) {
|
||||
if (!layerIsOn("toggleLabels")) {
|
||||
turnButtonOn("toggleLabels");
|
||||
|
|
@ -1620,21 +1670,21 @@ function drawEmblems() {
|
|||
const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa && b.coaSize != 0);
|
||||
|
||||
const getStateEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 40, 10, 100);
|
||||
const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier
|
||||
const sizeMod = +document.getElementById("emblemsStateSizeInput").value || 1;
|
||||
return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states
|
||||
};
|
||||
|
||||
const getProvinceEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 100, 5), 70);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 100, 5, 70);
|
||||
const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById("emblemsProvinceSizeInput").value || 1;
|
||||
return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces
|
||||
};
|
||||
|
||||
const getBurgEmblemSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 185, 2), 50);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 185, 2, 50);
|
||||
const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById("emblemsBurgSizeInput").value || 1;
|
||||
return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs
|
||||
|
|
@ -1684,15 +1734,21 @@ function drawEmblems() {
|
|||
}
|
||||
|
||||
const burgNodes = nodes.filter(node => node.type === "burg");
|
||||
const burgString = burgNodes.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join("");
|
||||
const burgString = burgNodes
|
||||
.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`)
|
||||
.join("");
|
||||
emblems.select("#burgEmblems").attr("font-size", sizeBurgs).html(burgString);
|
||||
|
||||
const provinceNodes = nodes.filter(node => node.type === "province");
|
||||
const provinceString = provinceNodes.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join("");
|
||||
const provinceString = provinceNodes
|
||||
.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`)
|
||||
.join("");
|
||||
emblems.select("#provinceEmblems").attr("font-size", sizeProvinces).html(provinceString);
|
||||
|
||||
const stateNodes = nodes.filter(node => node.type === "state");
|
||||
const stateString = stateNodes.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join("");
|
||||
const stateString = stateNodes
|
||||
.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`)
|
||||
.join("");
|
||||
emblems.select("#stateEmblems").attr("font-size", sizeStates).html(stateString);
|
||||
|
||||
invokeActiveZooming();
|
||||
|
|
|
|||
|
|
@ -1,287 +1,260 @@
|
|||
"use strict";
|
||||
function editMarker() {
|
||||
function editMarker(markerI) {
|
||||
if (customization) return;
|
||||
closeDialogs("#markerEditor, .stable");
|
||||
$("#markerEditor").dialog();
|
||||
closeDialogs(".stable");
|
||||
|
||||
const [element, marker] = getElement(markerI, d3.event);
|
||||
if (!marker || !element) return;
|
||||
|
||||
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
|
||||
if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
|
||||
|
||||
// dom elements
|
||||
const markerType = document.getElementById("markerType");
|
||||
const markerIcon = document.getElementById("markerIcon");
|
||||
const markerIconSelect = document.getElementById("markerIconSelect");
|
||||
const markerIconSize = document.getElementById("markerIconSize");
|
||||
const markerIconShiftX = document.getElementById("markerIconShiftX");
|
||||
const markerIconShiftY = document.getElementById("markerIconShiftY");
|
||||
const markerSize = document.getElementById("markerSize");
|
||||
const markerPin = document.getElementById("markerPin");
|
||||
const markerFill = document.getElementById("markerFill");
|
||||
const markerStroke = document.getElementById("markerStroke");
|
||||
|
||||
const markerNotes = document.getElementById("markerNotes");
|
||||
const markerLock = document.getElementById("markerLock");
|
||||
const addMarker = document.getElementById("addMarker");
|
||||
const markerAdd = document.getElementById("markerAdd");
|
||||
const markerRemove = document.getElementById("markerRemove");
|
||||
|
||||
elSelected = d3.select(d3.event.target).call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
updateInputs();
|
||||
|
||||
if (modules.editMarker) return;
|
||||
modules.editMarker = true;
|
||||
|
||||
$("#markerEditor").dialog({
|
||||
title: "Edit Marker", resizable: false,
|
||||
position: {my: "center top+30", at: "bottom", of: d3.event, collision: "fit"},
|
||||
title: "Edit Marker",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
close: closeMarkerEditor
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("markerGroup").addEventListener("click", toggleGroupSection);
|
||||
document.getElementById("markerAddGroup").addEventListener("click", toggleGroupInput);
|
||||
document.getElementById("markerSelectGroup").addEventListener("change", changeGroup);
|
||||
document.getElementById("markerInputGroup").addEventListener("change", createGroup);
|
||||
document.getElementById("markerRemoveGroup").addEventListener("click", removeGroup);
|
||||
const listeners = [
|
||||
listen(markerType, "change", changeMarkerType),
|
||||
listen(markerIcon, "input", changeMarkerIcon),
|
||||
listen(markerIconSelect, "click", selectMarkerIcon),
|
||||
listen(markerIconSize, "input", changeIconSize),
|
||||
listen(markerIconShiftX, "input", changeIconShiftX),
|
||||
listen(markerIconShiftY, "input", changeIconShiftY),
|
||||
listen(markerSize, "input", changeMarkerSize),
|
||||
listen(markerPin, "change", changeMarkerPin),
|
||||
listen(markerFill, "input", changePinFill),
|
||||
listen(markerStroke, "input", changePinStroke),
|
||||
listen(markerNotes, "click", editMarkerLegend),
|
||||
listen(markerLock, "click", toggleMarkerLock),
|
||||
listen(markerAdd, "click", toggleAddMarker),
|
||||
listen(markerRemove, "click", confirmMarkerDeletion)
|
||||
];
|
||||
|
||||
document.getElementById("markerIcon").addEventListener("click", toggleIconSection);
|
||||
document.getElementById("markerIconSize").addEventListener("input", changeIconSize);
|
||||
document.getElementById("markerIconShiftX").addEventListener("input", changeIconShiftX);
|
||||
document.getElementById("markerIconShiftY").addEventListener("input", changeIconShiftY);
|
||||
document.getElementById("markerIconSelect").addEventListener("click", selectMarkerIcon);
|
||||
function getElement(markerI, event) {
|
||||
if (event) {
|
||||
const element = event.target?.closest("svg");
|
||||
const marker = pack.markers.find(({i}) => Number(element.id.slice(6)) === i);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById("markerStyle").addEventListener("click", toggleStyleSection);
|
||||
document.getElementById("markerSize").addEventListener("input", changeMarkerSize);
|
||||
document.getElementById("markerBaseStroke").addEventListener("input", changePinStroke);
|
||||
document.getElementById("markerBaseFill").addEventListener("input", changePinFill);
|
||||
document.getElementById("markerIconStrokeWidth").addEventListener("input", changeIconStrokeWidth);
|
||||
document.getElementById("markerIconStroke").addEventListener("input", changeIconStroke);
|
||||
document.getElementById("markerIconFill").addEventListener("input", changeIconFill);
|
||||
const element = document.getElementById(`marker${markerI}`);
|
||||
const marker = pack.markers.find(({i}) => i === markerI);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById("markerToggleBubble").addEventListener("click", togglePinVisibility);
|
||||
document.getElementById("markerLegendButton").addEventListener("click", editMarkerLegend);
|
||||
document.getElementById("markerAdd").addEventListener("click", toggleAddMarker);
|
||||
document.getElementById("markerRemove").addEventListener("click", removeMarker);
|
||||
|
||||
updateGroupOptions();
|
||||
function getSameTypeMarkers() {
|
||||
const currentType = marker.type;
|
||||
if (!currentType) return [marker];
|
||||
return pack.markers.filter(({type}) => type === currentType);
|
||||
}
|
||||
|
||||
function dragMarker() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`;
|
||||
this.setAttribute("transform", transform);
|
||||
const dx = +this.getAttribute("x") - d3.event.x;
|
||||
const dy = +this.getAttribute("y") - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", dx + x);
|
||||
this.setAttribute("y", dy + y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", rn(dx + x, 2));
|
||||
this.setAttribute("y", rn(dy + y, 2));
|
||||
|
||||
const size = marker.size || 30;
|
||||
const zoomSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
|
||||
marker.x = rn(x + dx + zoomSize / 2, 1);
|
||||
marker.y = rn(y + dy + zoomSize, 1);
|
||||
marker.cell = findCell(marker.x, marker.y);
|
||||
});
|
||||
}
|
||||
|
||||
function updateInputs() {
|
||||
const id = elSelected.attr("data-id");
|
||||
const symbol = d3.select("#defs-markers").select(id);
|
||||
const icon = symbol.select("text");
|
||||
const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
|
||||
|
||||
markerSelectGroup.value = id.slice(1);
|
||||
markerIconSize.value = parseFloat(icon.attr("font-size"));
|
||||
markerIconShiftX.value = parseFloat(icon.attr("x"));
|
||||
markerIconShiftY.value = parseFloat(icon.attr("y"));
|
||||
markerType.value = type;
|
||||
markerIcon.value = icon;
|
||||
markerIconSize.value = px;
|
||||
markerIconShiftX.value = dx;
|
||||
markerIconShiftY.value = dy;
|
||||
markerSize.value = size;
|
||||
markerPin.value = pin;
|
||||
markerFill.value = fill;
|
||||
markerStroke.value = stroke;
|
||||
|
||||
markerSize.value = elSelected.attr("data-size");
|
||||
markerBaseStroke.value = symbol.select("path").attr("fill");
|
||||
markerBaseFill.value = symbol.select("circle").attr("fill");
|
||||
|
||||
markerIconStrokeWidth.value = icon.attr("stroke-width");
|
||||
markerIconStroke.value = icon.attr("stroke");
|
||||
markerIconFill.value = icon.attr("fill");
|
||||
|
||||
markerToggleBubble.className = symbol.select("circle").attr("opacity") === "0" ? "icon-info" : "icon-info-circled";
|
||||
markerIconSelect.innerHTML = icon.text();
|
||||
markerLock.className = lock ? "icon-lock" : "icon-lock-open";
|
||||
}
|
||||
|
||||
function toggleGroupSection() {
|
||||
if (markerGroupSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerGroup)").forEach(b => b.style.display = "inline-block");
|
||||
markerGroupSection.style.display = "none";
|
||||
} else {
|
||||
markerEditor.querySelectorAll("button:not(#markerGroup)").forEach(b => b.style.display = "none");
|
||||
markerGroupSection.style.display = "inline-block";
|
||||
}
|
||||
function changeMarkerType() {
|
||||
marker.type = this.value;
|
||||
}
|
||||
|
||||
function updateGroupOptions() {
|
||||
markerSelectGroup.innerHTML = "";
|
||||
d3.select("#defs-markers").selectAll("symbol").each(function() {
|
||||
markerSelectGroup.options.add(new Option(this.id, this.id));
|
||||
function changeMarkerIcon() {
|
||||
const icon = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
markerSelectGroup.value = elSelected.attr("data-id").slice(1);
|
||||
}
|
||||
|
||||
function toggleGroupInput() {
|
||||
if (markerInputGroup.style.display === "inline-block") {
|
||||
markerSelectGroup.style.display = "inline-block";
|
||||
markerInputGroup.style.display = "none";
|
||||
} else {
|
||||
markerSelectGroup.style.display = "none";
|
||||
markerInputGroup.style.display = "inline-block";
|
||||
markerInputGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
elSelected.attr("xlink:href", "#"+this.value);
|
||||
elSelected.attr("data-id", "#"+this.value);
|
||||
}
|
||||
|
||||
function createGroup() {
|
||||
let newGroup = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
||||
if (Number.isFinite(+newGroup.charAt(0))) newGroup = "m" + newGroup;
|
||||
if (document.getElementById(newGroup)) {
|
||||
tip("Element with this id already exists. Please provide a unique name", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
markerInputGroup.value = "";
|
||||
// clone old group assigning new id
|
||||
const id = elSelected.attr("data-id");
|
||||
const clone = d3.select("#defs-markers").select(id).node().cloneNode(true);
|
||||
clone.id = newGroup;
|
||||
document.getElementById("defs-markers").insertBefore(clone, null);
|
||||
elSelected.attr("xlink:href", "#"+newGroup).attr("data-id", "#"+newGroup);
|
||||
|
||||
// select new group
|
||||
markerSelectGroup.options.add(new Option(newGroup, newGroup, false, true));
|
||||
toggleGroupInput();
|
||||
}
|
||||
|
||||
function removeGroup() {
|
||||
const id = elSelected.attr("data-id");
|
||||
const used = document.querySelectorAll("use[data-id='"+id+"']");
|
||||
const count = used.length === 1 ? "1 element" : used.length + " elements";
|
||||
alertMessage.innerHTML = "Are you sure you want to remove all markers of that type (" + count + ")?";
|
||||
|
||||
$("#alert").dialog({resizable: false, title: "Remove marker type",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
if (id !== "#marker0") d3.select("#defs-markers").select(id).remove();
|
||||
used.forEach(e => {
|
||||
const index = notes.findIndex(n => n.id === e.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
e.remove();
|
||||
});
|
||||
updateGroupOptions();
|
||||
updateGroupOptions();
|
||||
$("#markerEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleIconSection() {
|
||||
if (markerIconSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "inline-block");
|
||||
markerIconSection.style.display = "none";
|
||||
markerIconSelect.style.display = "none";
|
||||
} else {
|
||||
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "none");
|
||||
markerIconSection.style.display = "inline-block";
|
||||
markerIconSelect.style.display = "inline-block";
|
||||
}
|
||||
}
|
||||
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(this.innerHTML, v => {
|
||||
this.innerHTML = v;
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").text(v);
|
||||
selectIcon(marker.icon, icon => {
|
||||
markerIcon.value = icon;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconSize() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("font-size", this.value + "px");
|
||||
const px = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.px = px;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftX() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("x", this.value + "%");
|
||||
const dx = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dx = dx;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftY() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("y", this.value + "%");
|
||||
}
|
||||
|
||||
function toggleStyleSection() {
|
||||
if (markerStyleSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerStyle)").forEach(b => b.style.display = "inline-block");
|
||||
markerStyleSection.style.display = "none";
|
||||
} else {
|
||||
markerEditor.querySelectorAll("button:not(#markerStyle)").forEach(b => b.style.display = "none");
|
||||
markerStyleSection.style.display = "inline-block";
|
||||
}
|
||||
const dy = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dy = dy;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerSize() {
|
||||
const id = elSelected.attr("data-id");
|
||||
document.querySelectorAll("use[data-id='"+id+"']").forEach(e => {
|
||||
const x = +e.dataset.x, y = +e.dataset.y;
|
||||
const desired = e.dataset.size = +markerSize.value;
|
||||
const size = Math.max(desired * 5 + 25 / scale, 1);
|
||||
const size = +this.value;
|
||||
const rescale = +markers.attr("rescale");
|
||||
|
||||
e.setAttribute("x", x - size / 2);
|
||||
e.setAttribute("y", y - size / 2);
|
||||
e.setAttribute("width", size);
|
||||
e.setAttribute("height", size);
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.size = size;
|
||||
const {i, x, y, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
invokeActiveZooming();
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select(id).select("path").attr("fill", this.value);
|
||||
d3.select(id).select("circle").attr("stroke", this.value);
|
||||
function changeMarkerPin() {
|
||||
const pin = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.pin = pin;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinFill() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select(id).select("circle").attr("fill", this.value);
|
||||
}
|
||||
|
||||
function changeIconStrokeWidth() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("stroke-width", this.value);
|
||||
}
|
||||
|
||||
function changeIconStroke() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("stroke", this.value);
|
||||
}
|
||||
|
||||
function changeIconFill() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("fill", this.value);
|
||||
}
|
||||
|
||||
function togglePinVisibility() {
|
||||
const id = elSelected.attr("data-id");
|
||||
let show = 1;
|
||||
if (this.className === "icon-info-circled") {this.className = "icon-info"; show = 0; }
|
||||
else this.className = "icon-info-circled";
|
||||
d3.select(id).select("circle").attr("opacity", show);
|
||||
d3.select(id).select("path").attr("opacity", show);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = elSelected.attr("id");
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
document.getElementById("addMarker").click();
|
||||
}
|
||||
|
||||
function removeMarker() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the marker?";
|
||||
$("#alert").dialog({resizable: false, title: "Remove marker",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
const index = notes.findIndex(n => n.id === elSelected.attr("id"));
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
elSelected.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
const fill = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.fill = fill;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const stroke = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.stroke = stroke;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
|
||||
const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconElement) {
|
||||
iconElement.innerHTML = icon;
|
||||
iconElement.setAttribute("x", dx + "%");
|
||||
iconElement.setAttribute("y", dy + "%");
|
||||
iconElement.setAttribute("font-size", px + "px");
|
||||
}
|
||||
}
|
||||
|
||||
function redrawPin({i, hidden, pin = "bubble", fill = "#fff", stroke = "#000"}) {
|
||||
const pinGroup = !hidden && document.querySelector(`#marker${i} > g`);
|
||||
if (pinGroup) pinGroup.innerHTML = getPin(pin, fill, stroke);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = element.id;
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleMarkerLock() {
|
||||
marker.lock = !marker.lock;
|
||||
markerLock.classList.toggle("icon-lock-open");
|
||||
markerLock.classList.toggle("icon-lock");
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markerAdd.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function confirmMarkerDeletion() {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: deleteMarker
|
||||
});
|
||||
}
|
||||
|
||||
function deleteMarker() {
|
||||
notes = notes.filter(note => note.id !== element.id);
|
||||
pack.markers = pack.markers.filter(m => m.i !== marker.i);
|
||||
element.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function closeMarkerEditor() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
unselect();
|
||||
if (addMarker.classList.contains("pressed")) addMarker.classList.remove("pressed");
|
||||
if (markerAdd.classList.contains("pressed")) markerAdd.classList.remove("pressed");
|
||||
addMarker.classList.remove("pressed");
|
||||
markerAdd.classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
196
modules/ui/markers-overview.js
Normal file
196
modules/ui/markers-overview.js
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
"use strict";
|
||||
function overviewMarkers() {
|
||||
if (customization) return;
|
||||
closeDialogs("#markersOverview, .stable");
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
|
||||
const markerGroup = document.getElementById("markers");
|
||||
const body = document.getElementById("markersBody");
|
||||
const markersInverPin = document.getElementById("markersInverPin");
|
||||
const markersInverLock = document.getElementById("markersInverLock");
|
||||
const markersFooterNumber = document.getElementById("markersFooterNumber");
|
||||
const markersOverviewRefresh = document.getElementById("markersOverviewRefresh");
|
||||
const markersAddFromOverview = document.getElementById("markersAddFromOverview");
|
||||
const markersGenerationConfig = document.getElementById("markersGenerationConfig");
|
||||
const markersRemoveAll = document.getElementById("markersRemoveAll");
|
||||
const markersExport = document.getElementById("markersExport");
|
||||
|
||||
addLines();
|
||||
|
||||
$("#markersOverview").dialog({
|
||||
title: "Markers Overview",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: close,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
const listeners = [
|
||||
listen(body, "click", handleLineClick),
|
||||
listen(markersInverPin, "click", invertPin),
|
||||
listen(markersInverLock, "click", invertLock),
|
||||
listen(markersOverviewRefresh, "click", addLines),
|
||||
listen(markersAddFromOverview, "click", toggleAddMarker),
|
||||
listen(markersGenerationConfig, "click", configMarkersGeneration),
|
||||
listen(markersRemoveAll, "click", triggerRemoveAll),
|
||||
listen(markersExport, "click", exportMarkers)
|
||||
];
|
||||
|
||||
function handleLineClick(ev) {
|
||||
const el = ev.target;
|
||||
const i = +el.parentNode.dataset.i;
|
||||
|
||||
if (el.classList.contains("icon-pencil")) return openEditor(i);
|
||||
if (el.classList.contains("icon-dot-circled")) return focusOnMarker(i);
|
||||
if (el.classList.contains("icon-pin")) return pinMarker(el, i);
|
||||
if (el.classList.contains("locks")) return toggleLockStatus(el, i);
|
||||
if (el.classList.contains("icon-trash-empty")) return triggerRemove(i);
|
||||
}
|
||||
|
||||
function addLines() {
|
||||
const lines = pack.markers
|
||||
.map(({i, type, icon, pinned, lock}) => {
|
||||
return `<div class="states" data-i=${i} data-type="${type}">
|
||||
<div data-tip="Marker icon and type" style="width:12em">${icon} ${type}</div>
|
||||
<span style="padding-right:.1em" data-tip="Edit marker" class="icon-pencil"></span>
|
||||
<span style="padding-right:.1em" data-tip="Focus on marker position" class="icon-dot-circled pointer"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin ${pinned ? "" : "inactive"}" pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove marker" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
body.innerHTML = lines;
|
||||
markersFooterNumber.innerText = pack.markers.length;
|
||||
|
||||
applySorting(markersHeader);
|
||||
}
|
||||
|
||||
function invertPin() {
|
||||
let anyPinned = false;
|
||||
|
||||
pack.markers.forEach(marker => {
|
||||
const pinned = !marker.pinned;
|
||||
if (pinned) {
|
||||
marker.pinned = true;
|
||||
anyPinned = true;
|
||||
} else delete marker.pinned;
|
||||
});
|
||||
|
||||
markerGroup.setAttribute("pinned", anyPinned ? 1 : null);
|
||||
drawMarkers();
|
||||
addLines();
|
||||
}
|
||||
|
||||
function invertLock() {
|
||||
pack.markers = pack.markers.map(marker => ({...marker, lock: !marker.lock}));
|
||||
addLines();
|
||||
}
|
||||
|
||||
function openEditor(i) {
|
||||
const marker = pack.markers.find(marker => marker.i === i);
|
||||
if (!marker) return;
|
||||
|
||||
const {x, y} = marker;
|
||||
zoomTo(x, y, 8, 2000);
|
||||
editMarker(i);
|
||||
}
|
||||
|
||||
function focusOnMarker(i) {
|
||||
highlightElement(document.getElementById(`marker${i}`), 2);
|
||||
}
|
||||
|
||||
function pinMarker(el, i) {
|
||||
const marker = pack.markers.find(marker => marker.i === i);
|
||||
if (marker.pinned) {
|
||||
delete marker.pinned;
|
||||
const anyPinned = pack.markers.some(marker => marker.pinned);
|
||||
if (!anyPinned) markerGroup.removeAttribute("pinned");
|
||||
} else {
|
||||
marker.pinned = true;
|
||||
markerGroup.setAttribute("pinned", 1);
|
||||
}
|
||||
el.classList.toggle("inactive");
|
||||
drawMarkers();
|
||||
}
|
||||
|
||||
function toggleLockStatus(el, i) {
|
||||
const marker = pack.markers.find(marker => marker.i === i);
|
||||
if (marker.lock) {
|
||||
delete marker.lock;
|
||||
el.className = "locks pointer icon-lock-open inactive";
|
||||
} else {
|
||||
marker.lock = true;
|
||||
el.className = "locks pointer icon-lock";
|
||||
}
|
||||
}
|
||||
|
||||
function triggerRemove(i) {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: () => removeMarker(i)
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markersAddFromOverview.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function removeMarker(i) {
|
||||
notes = notes.filter(note => note.id !== `marker${i}`);
|
||||
pack.markers = pack.markers.filter(marker => marker.i !== i);
|
||||
document.getElementById(`marker${i}`)?.remove();
|
||||
addLines();
|
||||
}
|
||||
|
||||
function triggerRemoveAll() {
|
||||
confirmationDialog({
|
||||
title: "Remove all markers",
|
||||
message: "Are you sure you want to remove all non-locked markers? The action cannot be reverted",
|
||||
confirm: "Remove all",
|
||||
onConfirm: removeAllMarkers
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllMarkers() {
|
||||
pack.markers = pack.markers.filter(({i, lock}) => {
|
||||
if (lock) return true;
|
||||
|
||||
const id = `marker${i}`;
|
||||
document.getElementById(id)?.remove();
|
||||
notes = notes.filter(note => note.id !== id);
|
||||
return false;
|
||||
});
|
||||
|
||||
addLines();
|
||||
}
|
||||
|
||||
function exportMarkers() {
|
||||
const headers = "Id,Type,Icon,Name,Note,X,Y\n";
|
||||
|
||||
const body = pack.markers.map(marker => {
|
||||
const {i, type, icon, x, y} = marker;
|
||||
const id = `marker${i}`;
|
||||
const note = notes.find(note => note.id === id);
|
||||
const legend = escape(note.legend);
|
||||
return [id, type, icon, note.name, legend, x, y].join(",");
|
||||
});
|
||||
|
||||
const data = headers + body.join("\n");
|
||||
const fileName = getFileName("Markers") + ".csv";
|
||||
downloadFile(data, fileName);
|
||||
}
|
||||
|
||||
function close() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
addMarker.classList.remove("pressed");
|
||||
markerAdd.classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,16 @@ class Rulers {
|
|||
for (const rulerString of rulers) {
|
||||
const [type, pointsString] = rulerString.split(": ");
|
||||
const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
|
||||
const Type = type === "Ruler" ? Ruler : type === "Opisometer" ? Opisometer : type === "RouteOpisometer" ? RouteOpisometer : type === "Planimeter" ? Planimeter : null;
|
||||
const Type =
|
||||
type === "Ruler"
|
||||
? Ruler
|
||||
: type === "Opisometer"
|
||||
? Opisometer
|
||||
: type === "RouteOpisometer"
|
||||
? RouteOpisometer
|
||||
: type === "Planimeter"
|
||||
? Planimeter
|
||||
: null;
|
||||
this.create(Type, points);
|
||||
}
|
||||
}
|
||||
|
|
@ -527,17 +536,18 @@ class Planimeter extends Measurer {
|
|||
}
|
||||
|
||||
// Scale bar
|
||||
function drawScaleBar() {
|
||||
function drawScaleBar(requestedScale) {
|
||||
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
|
||||
scaleBar.selectAll("*").remove(); // fully redraw every time
|
||||
const scaleLevel = requestedScale || scale;
|
||||
|
||||
const dScale = distanceScaleInput.value;
|
||||
const distanceScale = distanceScaleInput.value;
|
||||
const unit = distanceUnitInput.value;
|
||||
const size = +barSizeInput.value;
|
||||
|
||||
// calculate size
|
||||
const init = 100; // actual length in pixels if scale, dScale and size = 1;
|
||||
const size = +barSizeInput.value;
|
||||
let val = (init * size * dScale) / scale; // bar length in distance unit
|
||||
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);
|
||||
|
|
@ -545,13 +555,13 @@ function drawScaleBar() {
|
|||
else if (val > 9) val = rn(val, -1);
|
||||
// round to 10
|
||||
else val = rn(val); // round to 1
|
||||
const l = (val * scale) / dScale; // actual length in pixels on this scale
|
||||
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
|
||||
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0.5)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size - 0.5)
|
||||
.attr("x2", length + size - 0.5)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "white");
|
||||
|
|
@ -559,16 +569,16 @@ function drawScaleBar() {
|
|||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", size)
|
||||
.attr("x2", l + size)
|
||||
.attr("x2", length + size)
|
||||
.attr("y2", size)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
const dash = size + " " + rn(l / 5 - size, 2);
|
||||
const dash = size + " " + rn(length / 5 - size, 2);
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size)
|
||||
.attr("x2", length + size)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", rn(size * 3, 2))
|
||||
.attr("stroke-dasharray", dash)
|
||||
|
|
@ -580,16 +590,16 @@ function drawScaleBar() {
|
|||
.data(d3.range(0, 6))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", d => rn((d * l) / 5, 2))
|
||||
.attr("x", d => rn((d * length) / 5, 2))
|
||||
.attr("y", 0)
|
||||
.attr("dy", "-.5em")
|
||||
.attr("font-size", fontSize)
|
||||
.text(d => rn((((d * l) / 5) * dScale) / scale) + (d < 5 ? "" : " " + unit));
|
||||
.text(d => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? "" : " " + unit));
|
||||
|
||||
if (barLabel.value !== "") {
|
||||
scaleBar
|
||||
.append("text")
|
||||
.attr("x", (l + 1) / 2)
|
||||
.attr("x", (length + 1) / 2)
|
||||
.attr("y", 2 * size)
|
||||
.attr("dominant-baseline", "text-before-edge")
|
||||
.attr("font-size", fontSize)
|
||||
|
|
|
|||
|
|
@ -75,14 +75,21 @@ function overviewMilitary() {
|
|||
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(" ");
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
lines += `<div class="states" data-id=${s.i} data-state="${
|
||||
s.name
|
||||
}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
s.color
|
||||
}" class="fillRect"></svg>
|
||||
<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="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>
|
||||
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(s.alert, 2)}">
|
||||
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(
|
||||
s.alert,
|
||||
2
|
||||
)}">
|
||||
<span data-tip="Show regiments list" class="icon-list-bullet pointer"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -145,7 +152,15 @@ function overviewMilitary() {
|
|||
if (!layerIsOn("toggleStates")) return;
|
||||
const d = regions.select("#state" + state).attr("d");
|
||||
|
||||
const path = debug.append("path").attr("class", "highlight").attr("d", d).attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1).attr("filter", "url(#blur1)");
|
||||
const path = debug
|
||||
.append("path")
|
||||
.attr("class", "highlight")
|
||||
.attr("d", d)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
const l = path.node().getTotalLength(),
|
||||
dur = (l + 5000) / 2;
|
||||
|
|
@ -199,9 +214,9 @@ function overviewMilitary() {
|
|||
|
||||
function militaryCustomize() {
|
||||
const types = ["melee", "ranged", "mounted", "machinery", "naval", "armored", "aviation", "magical"];
|
||||
const table = document.getElementById("militaryOptions").querySelector("tbody");
|
||||
const tableBody = document.getElementById("militaryOptions").querySelector("tbody");
|
||||
removeUnitLines();
|
||||
options.military.map(u => addUnitLine(u));
|
||||
options.military.map(unit => addUnitLine(unit));
|
||||
|
||||
$("#militaryOptions").dialog({
|
||||
title: "Edit Military Units",
|
||||
|
|
@ -218,43 +233,135 @@ function overviewMilitary() {
|
|||
},
|
||||
open: function () {
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
|
||||
buttons[0].addEventListener("mousemove", () =>
|
||||
tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>")
|
||||
);
|
||||
buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table"));
|
||||
buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes"));
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.overviewMilitaryCustomize) return;
|
||||
modules.overviewMilitaryCustomize = true;
|
||||
|
||||
tableBody.addEventListener("click", event => {
|
||||
const el = event.target;
|
||||
if (el.tagName !== "BUTTON") return;
|
||||
const type = el.dataset.type;
|
||||
|
||||
if (type === "icon") return selectIcon(el.innerHTML, v => (el.innerHTML = v));
|
||||
if (type === "biomes") {
|
||||
const {i, name, color} = biomesData;
|
||||
const biomesArray = Array(i.length).fill(null);
|
||||
const biomes = biomesArray.map((_, i) => ({i, name: name[i], color: color[i]}));
|
||||
return selectLimitation(el, biomes);
|
||||
}
|
||||
if (type === "states") return selectLimitation(el, pack.states);
|
||||
if (type === "cultures") return selectLimitation(el, pack.cultures);
|
||||
if (type === "religions") return selectLimitation(el, pack.religions);
|
||||
});
|
||||
|
||||
function removeUnitLines() {
|
||||
table.querySelectorAll("tr").forEach(el => el.remove());
|
||||
tableBody.querySelectorAll("tr").forEach(el => el.remove());
|
||||
}
|
||||
|
||||
function addUnitLine(u) {
|
||||
function getLimitValue(attr) {
|
||||
return attr?.join(",") || "";
|
||||
}
|
||||
|
||||
function getLimitText(attr) {
|
||||
return attr?.length ? "some" : "all";
|
||||
}
|
||||
|
||||
function getLimitTip(attr, data) {
|
||||
if (!attr || !attr.length) return "";
|
||||
return attr.map(i => data?.[i]?.name || "").join(", ");
|
||||
}
|
||||
|
||||
function addUnitLine(unit) {
|
||||
const {type, icon, name, rural, urban, power, crew, separate} = unit;
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon || " "}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types.map(t => `<option ${u.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ")}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? "checked" : ""}>
|
||||
<label for="${u.name}Separate" class="checkbox-label"></label></td>
|
||||
const typeOptions = types.map(t => `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ");
|
||||
|
||||
const getLimitButton = attr =>
|
||||
`<button
|
||||
data-tip="Select allowed ${attr}"
|
||||
data-type="${attr}"
|
||||
title="${getLimitTip(unit[attr], pack[attr])}"
|
||||
data-value="${getLimitValue(unit[attr])}">
|
||||
${getLimitText(unit[attr])}
|
||||
</button>`;
|
||||
|
||||
row.innerHTML = `<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>
|
||||
<td>${getLimitButton("cultures")}</td>
|
||||
<td>${getLimitButton("religions")}</td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min=1 step=1 value="${crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${typeOptions}</select></td>
|
||||
<td data-tip="Check if unit is <b>separate</b> and can be stacked only with the same units">
|
||||
<input id="${name}Separate" type="checkbox" class="checkbox" ${separate ? "checked" : ""}>
|
||||
<label for="${name}Separate" class="checkbox-label"></label></td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
|
||||
row.querySelector("button").addEventListener("click", function (e) {
|
||||
selectIcon(this.innerHTML, v => (this.innerHTML = v));
|
||||
});
|
||||
table.appendChild(row);
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
removeUnitLines();
|
||||
Military.getDefaultOptions().map(u => addUnitLine(u));
|
||||
Military.getDefaultOptions().map(unit => addUnitLine(unit));
|
||||
}
|
||||
|
||||
function selectLimitation(el, data) {
|
||||
const type = el.dataset.type;
|
||||
const value = el.dataset.value;
|
||||
const initial = value ? value.split(",").map(v => +v) : [];
|
||||
|
||||
const filtered = data.filter(datum => datum.i && !datum.removed);
|
||||
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" : ""} >
|
||||
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
||||
</td></tr>`
|
||||
);
|
||||
alertMessage.innerHTML = `<b>Limit unit by ${type}:</b><table style="margin-top:.3em"><tbody>${lines.join("")}</tbody></table>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
width: fitContent(),
|
||||
title: `Limit unit`,
|
||||
buttons: {
|
||||
Invert: function () {
|
||||
alertMessage.querySelectorAll("input").forEach(el => (el.checked = !el.checked));
|
||||
},
|
||||
Apply: function () {
|
||||
const inputs = Array.from(alertMessage.querySelectorAll("input"));
|
||||
const selected = inputs.reduce((acc, input) => {
|
||||
if (input.checked) acc.push(input.dataset.i);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (!selected.length) return tip("Select at least one element", false, "error");
|
||||
|
||||
const allAreSelected = selected.length === inputs.length;
|
||||
el.dataset.value = allAreSelected ? "" : selected.join(",");
|
||||
el.innerHTML = allAreSelected ? "all" : "some";
|
||||
el.setAttribute("title", getLimitTip(selected, data));
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(table.querySelectorAll("tr"));
|
||||
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");
|
||||
|
|
@ -263,14 +370,22 @@ function overviewMilitary() {
|
|||
|
||||
$("#militaryOptions").dialog("close");
|
||||
options.military = unitLines.map((r, i) => {
|
||||
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll("input, select, button")).map(d => {
|
||||
let value = d.value;
|
||||
if (d.type === "number") value = +d.value || 0;
|
||||
if (d.type === "checkbox") value = +d.checked || 0;
|
||||
if (d.type === "button") value = d.innerHTML || "⠀";
|
||||
return value;
|
||||
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;
|
||||
});
|
||||
return {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
|
||||
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
if (biomes) unit.biomes = biomes;
|
||||
if (states) unit.states = states;
|
||||
if (cultures) unit.cultures = cultures;
|
||||
if (religions) unit.religions = religions;
|
||||
return unit;
|
||||
});
|
||||
localStorage.setItem("military", JSON.stringify(options.military));
|
||||
Military.generate();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
function editNotes(id, name) {
|
||||
// update list of objects
|
||||
const select = document.getElementById("notesSelect");
|
||||
|
|
@ -8,11 +9,12 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
// initiate pell (html editor)
|
||||
const notesText = document.getElementById("notesText");
|
||||
notesText.innerHTML = "";
|
||||
const editor = Pell.init({
|
||||
element: document.getElementById("notesText"),
|
||||
element: notesText,
|
||||
onChange: html => {
|
||||
const id = document.getElementById("notesSelect").value;
|
||||
const note = notes.find(note => note.id === id);
|
||||
const note = notes.find(note => note.id === select.value);
|
||||
if (!note) return;
|
||||
note.legend = html;
|
||||
showNote(note);
|
||||
|
|
@ -43,8 +45,7 @@ function editNotes(id, name) {
|
|||
title: "Notes Editor",
|
||||
minWidth: "40em",
|
||||
width: "50vw",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
close: () => (notesText.innerHTML = "")
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
|
||||
if (modules.editNotes) return;
|
||||
|
|
@ -108,7 +109,7 @@ function editNotes(id, name) {
|
|||
return;
|
||||
}
|
||||
|
||||
highlightElement(element); // if element is found
|
||||
highlightElement(element, 3); // if element is found
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
|
|
|
|||
|
|
@ -89,19 +89,20 @@ function showSupporters() {
|
|||
Maxwell Hill,Drunken_Legends,rob bee,Jesse Holmes,YYako,Detocroix,Anoplexian,Hannah,Paul,Sandra Krohn,Lucid,Richard Keating,Allen Varney,Rick Falkvinge,
|
||||
Seth Fusion,Adam Butler,Gus,StroboWolf,Sadie Blackthorne,Zewen Senpai,Dell McKnight,Oneiris,Darinius Dragonclaw Studios,Christopher Whitney,Rhodes HvZ,
|
||||
Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,
|
||||
Thirty-OneR ,ThatGuyGW ,Dee Chiu,MontyBoosh ,Achillain ,Jaden ,SashaTK,Steve Johnson,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR,
|
||||
ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill,
|
||||
Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,Alex Debus,Joshua Vaught,
|
||||
Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,Radovan Zapletal,Jmmat6,
|
||||
Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen,
|
||||
Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone,
|
||||
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram,
|
||||
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
||||
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,
|
||||
Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,
|
||||
George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,Mike Conley,Xavier privé,Hope You're Well,
|
||||
Mark Sprietsma,Robert Landry,Nick Mowry"`;
|
||||
Thirty-OneR,ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,
|
||||
Andrew Rostaing,Daniel Gill,Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,
|
||||
Alex Debus,Joshua Vaught,Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,
|
||||
Radovan Zapletal,Jmmat6,Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,
|
||||
Guilherme Aguiar,Jarno Hallikainen,Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,
|
||||
Cooper Counts,Patrick Jones,Clonetone,PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,
|
||||
Page One Project,Spencer Morris,Paul Ingram,Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,
|
||||
Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,
|
||||
PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,
|
||||
Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,
|
||||
Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,
|
||||
Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky,
|
||||
Dario Spadavecchia,Bas Kroot,John Patrick Callahan Jr,Alexandra Vesey,D,Exp1nt,james,Braxton Istace,w,Rurikid,AntiBlock,Redsauz,BigE0021,
|
||||
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, "")
|
||||
|
|
@ -150,7 +151,9 @@ optionsContent.addEventListener("input", function (event) {
|
|||
else if (id === "regionsInput" || id === "regionsOutput") changeStatesNumber(value);
|
||||
else if (id === "emblemShape") changeEmblemShape(value);
|
||||
else if (id === "tooltipSizeInput" || id === "tooltipSizeOutput") changeTooltipSize(value);
|
||||
else if (id === "transparencyInput") changeDialogsTransparency(value);
|
||||
else if (id === "themeHueInput") changeThemeHue(value);
|
||||
else if (id === "themeColorInput") changeDialogsTheme(themeColorInput.value, transparencyInput.value);
|
||||
else if (id === "transparencyInput") changeDialogsTheme(themeColorInput.value, value);
|
||||
});
|
||||
|
||||
optionsContent.addEventListener("change", function (event) {
|
||||
|
|
@ -158,23 +161,24 @@ optionsContent.addEventListener("change", function (event) {
|
|||
const value = event.target.value;
|
||||
|
||||
if (id === "zoomExtentMin" || id === "zoomExtentMax") changeZoomExtent(value);
|
||||
else if (id === "optionsSeed") generateMapWithSeed();
|
||||
else if (id === "optionsSeed") generateMapWithSeed("seed change");
|
||||
else if (id === "uiSizeInput" || id === "uiSizeOutput") changeUIsize(value);
|
||||
if (id === "shapeRendering") viewbox.attr("shape-rendering", value);
|
||||
else if (id === "yearInput") changeYear();
|
||||
else if (id === "eraInput") changeEra();
|
||||
else if (id === "stateLabelsModeInput") options.stateLabelsMode = value;
|
||||
});
|
||||
|
||||
optionsContent.addEventListener("click", function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === "toggleFullscreen") toggleFullscreen();
|
||||
else if (id === "optionsSeedGenerate") generateMapWithSeed();
|
||||
else if (id === "optionsMapHistory") showSeedHistoryDialog();
|
||||
else if (id === "optionsCopySeed") copyMapURL();
|
||||
else if (id === "optionsEraRegenerate") regenerateEra();
|
||||
else if (id === "zoomExtentDefault") restoreDefaultZoomExtent();
|
||||
else if (id === "translateExtent") toggleTranslateExtent(event.target);
|
||||
else if (id === "speakerTest") testSpeaker();
|
||||
else if (id === "themeColorRestore") restoreDefaultThemeColor();
|
||||
});
|
||||
|
||||
function mapSizeInputChange() {
|
||||
|
|
@ -208,8 +212,8 @@ function changeMapSize() {
|
|||
|
||||
// just apply canvas size that was already set
|
||||
function applyMapSize() {
|
||||
const zoomMin = +zoomExtentMin.value,
|
||||
zoomMax = +zoomExtentMax.value;
|
||||
const zoomMin = +zoomExtentMin.value;
|
||||
const zoomMax = +zoomExtentMax.value;
|
||||
graphWidth = +mapWidthInput.value;
|
||||
graphHeight = +mapHeightInput.value;
|
||||
svgWidth = Math.min(graphWidth, window.innerWidth);
|
||||
|
|
@ -277,12 +281,9 @@ function testSpeaker() {
|
|||
speechSynthesis.speak(speaker);
|
||||
}
|
||||
|
||||
function generateMapWithSeed() {
|
||||
if (optionsSeed.value == seed) {
|
||||
tip("The current map already has this seed", false, "error");
|
||||
return;
|
||||
}
|
||||
regeneratePrompt();
|
||||
function generateMapWithSeed(source) {
|
||||
if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error");
|
||||
regeneratePrompt(source);
|
||||
}
|
||||
|
||||
function showSeedHistoryDialog() {
|
||||
|
|
@ -313,7 +314,7 @@ function restoreSeed(id) {
|
|||
mapHeightInput.value = mapHistory[id].height;
|
||||
templateInput.value = mapHistory[id].template;
|
||||
if (locked("template")) unlock("template");
|
||||
regeneratePrompt();
|
||||
regeneratePrompt("seed history");
|
||||
}
|
||||
|
||||
function restoreDefaultZoomExtent() {
|
||||
|
|
@ -417,7 +418,7 @@ function changeUIsize(value) {
|
|||
if (+value > max) value = max;
|
||||
|
||||
uiSizeInput.value = uiSizeOutput.value = value;
|
||||
document.getElementsByTagName("body")[0].style.fontSize = value * 11 + "px";
|
||||
document.getElementsByTagName("body")[0].style.fontSize = rn(value * 10, 2) + "px";
|
||||
document.getElementById("options").style.width = value * 300 + "px";
|
||||
}
|
||||
|
||||
|
|
@ -429,26 +430,56 @@ function changeTooltipSize(value) {
|
|||
tooltip.style.fontSize = `calc(${value}px + 0.5vw)`;
|
||||
}
|
||||
|
||||
// change transparency for modal windows
|
||||
function changeDialogsTransparency(value) {
|
||||
transparencyInput.value = transparencyOutput.value = value;
|
||||
const alpha = (100 - +value) / 100;
|
||||
const optionsColor = "rgba(164, 139, 149, " + alpha + ")";
|
||||
const dialogsColor = "rgba(255, 255, 255, " + alpha + ")";
|
||||
const optionButtonsColor = "rgba(145, 110, 127, " + Math.min(alpha + 0.3, 1) + ")";
|
||||
const optionLiColor = "rgba(153, 123, 137, " + Math.min(alpha + 0.3, 1) + ")";
|
||||
document.getElementById("options").style.backgroundColor = optionsColor;
|
||||
document.getElementById("dialogs").style.backgroundColor = dialogsColor;
|
||||
document.querySelectorAll(".tabcontent button").forEach(el => (el.style.backgroundColor = optionButtonsColor));
|
||||
document.querySelectorAll(".tabcontent li").forEach(el => (el.style.backgroundColor = optionLiColor));
|
||||
document.querySelectorAll("button.options").forEach(el => (el.style.backgroundColor = optionLiColor));
|
||||
const THEME_COLOR = "#997787";
|
||||
function restoreDefaultThemeColor() {
|
||||
localStorage.removeItem("themeColor");
|
||||
changeDialogsTheme(THEME_COLOR, transparencyInput.value);
|
||||
}
|
||||
|
||||
function changeThemeHue(hue) {
|
||||
const {s, l} = d3.hsl(themeColorInput.value);
|
||||
const newColor = d3.hsl(+hue, s, l).hex();
|
||||
changeDialogsTheme(newColor, transparencyInput.value);
|
||||
}
|
||||
|
||||
// change color and transparency for modal windows
|
||||
function changeDialogsTheme(themeColor, transparency) {
|
||||
transparencyInput.value = transparencyOutput.value = transparency;
|
||||
const alpha = (100 - +transparency) / 100;
|
||||
const alphaReduced = Math.min(alpha + 0.3, 1);
|
||||
|
||||
const {h, s, l} = d3.hsl(themeColor || THEME_COLOR);
|
||||
themeColorInput.value = themeColor || THEME_COLOR;
|
||||
themeHueInput.value = h;
|
||||
|
||||
const getRGBA = (hue, saturation, lightness, alpha) => {
|
||||
const color = d3.hsl(hue, saturation, lightness, alpha);
|
||||
return color.toString();
|
||||
};
|
||||
|
||||
const theme = [
|
||||
{name: "--bg-main", h, s, l, alpha},
|
||||
{name: "--bg-lighter", h, s, l: l + 0.02, alpha},
|
||||
{name: "--bg-light", h, s: s - 0.02, l: l + 0.06, alpha},
|
||||
{name: "--light-solid", h, s: s + 0.01, l: l + 0.05, alpha: 1},
|
||||
{name: "--dark-solid", h, s, l: l - 0.2, alpha: 1},
|
||||
{name: "--header", h, s: s, l: l - 0.03, alpha: alphaReduced},
|
||||
{name: "--header-active", h, s: s, l: l - 0.09, alpha: alphaReduced},
|
||||
{name: "--bg-disabled", h, s: s - 0.04, l: l + 0.09, alphaReduced},
|
||||
{name: "--bg-dialogs", h: 0, s: 0, l: 0.98, alpha}
|
||||
];
|
||||
|
||||
const sx = document.documentElement.style;
|
||||
theme.forEach(({name, h, s, l, alpha}) => {
|
||||
sx.setProperty(name, getRGBA(h, s, l, alpha));
|
||||
});
|
||||
}
|
||||
|
||||
function changeZoomExtent(value) {
|
||||
const min = Math.max(+zoomExtentMin.value, 0.01),
|
||||
max = Math.min(+zoomExtentMax.value, 200);
|
||||
const min = Math.max(+zoomExtentMin.value, 0.01);
|
||||
const max = Math.min(+zoomExtentMax.value, 200);
|
||||
zoom.scaleExtent([min, max]);
|
||||
const scale = Math.max(Math.min(+value, 200), 0.01);
|
||||
const scale = minmax(+value, 0.01, 200);
|
||||
zoom.scaleTo(svg, scale);
|
||||
}
|
||||
|
||||
|
|
@ -484,13 +515,12 @@ function applyStoredOptions() {
|
|||
.map(w => +w);
|
||||
if (localStorage.getItem("military")) options.military = JSON.parse(localStorage.getItem("military"));
|
||||
|
||||
changeDialogsTransparency(localStorage.getItem("transparency") || 5);
|
||||
if (localStorage.getItem("tooltipSize")) changeTooltipSize(localStorage.getItem("tooltipSize"));
|
||||
if (localStorage.getItem("regions")) changeStatesNumber(localStorage.getItem("regions"));
|
||||
|
||||
uiSizeInput.max = uiSizeOutput.max = getUImaxSize();
|
||||
if (localStorage.getItem("uiSize")) changeUIsize(localStorage.getItem("uiSize"));
|
||||
else changeUIsize(Math.max(Math.min(rn(mapWidthInput.value / 1280, 1), 2.5), 1));
|
||||
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;
|
||||
|
|
@ -499,8 +529,14 @@ function applyStoredOptions() {
|
|||
if (width) mapWidthInput.value = width;
|
||||
if (height) mapHeightInput.value = height;
|
||||
|
||||
const transparency = localStorage.getItem("transparency") || 5;
|
||||
const themeColor = localStorage.getItem("themeColor");
|
||||
changeDialogsTheme(themeColor, transparency);
|
||||
|
||||
// set shape rendering
|
||||
viewbox.attr("shape-rendering", shapeRendering.value);
|
||||
|
||||
options.stateLabelsMode = stateLabelsModeInput.value;
|
||||
}
|
||||
|
||||
// randomize options if randomization is allowed (not locked or options='default')
|
||||
|
|
@ -531,10 +567,9 @@ function randomizeOptions() {
|
|||
|
||||
// 'Units Editor' settings
|
||||
const US = navigator.language === "en-US";
|
||||
const UK = navigator.language === "en-GB";
|
||||
if (randomize || !locked("distanceScale")) distanceScaleOutput.value = distanceScaleInput.value = gauss(3, 1, 1, 5);
|
||||
if (!stored("distanceUnit")) distanceUnitInput.value = US || UK ? "mi" : "km";
|
||||
if (!stored("heightUnit")) heightUnit.value = US || UK ? "ft" : "m";
|
||||
if (!stored("distanceUnit")) distanceUnitInput.value = US ? "mi" : "km";
|
||||
if (!stored("heightUnit")) heightUnit.value = US ? "ft" : "m";
|
||||
if (!stored("temperatureScale")) temperatureScale.value = US ? "°F" : "°C";
|
||||
|
||||
// World settings
|
||||
|
|
@ -621,23 +656,17 @@ function restoreDefaultOptions() {
|
|||
// Sticked menu Options listeners
|
||||
document.getElementById("sticked").addEventListener("click", function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === "newMapButton") regeneratePrompt();
|
||||
if (id === "newMapButton") regeneratePrompt("sticky button");
|
||||
else if (id === "saveButton") showSavePane();
|
||||
else if (id === "exportButton") showExportPane();
|
||||
else if (id === "loadButton") showLoadPane();
|
||||
else if (id === "zoomReset") resetZoom(1000);
|
||||
});
|
||||
|
||||
function regeneratePrompt() {
|
||||
if (customization) {
|
||||
tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
|
||||
return;
|
||||
}
|
||||
function regeneratePrompt(source) {
|
||||
if (customization) return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 5) {
|
||||
regenerateMap();
|
||||
return;
|
||||
}
|
||||
if (workingTime < 5) return regenerateMap(source);
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
||||
All unsaved changes made to the current map will be lost`;
|
||||
|
|
@ -650,7 +679,7 @@ function regeneratePrompt() {
|
|||
},
|
||||
Generate: function () {
|
||||
closeDialogs();
|
||||
regenerateMap();
|
||||
regenerateMap(source);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -770,6 +799,9 @@ function openSaveTiles() {
|
|||
status.innerHTML = "";
|
||||
let loading = null;
|
||||
|
||||
const inputs = document.getElementById("saveTilesScreen").querySelectorAll("input");
|
||||
inputs.forEach(input => input.addEventListener("input", updateTilesOptions));
|
||||
|
||||
$("#saveTilesScreen").dialog({
|
||||
resizable: false,
|
||||
title: "Download tiles",
|
||||
|
|
@ -790,17 +822,13 @@ function openSaveTiles() {
|
|||
}
|
||||
},
|
||||
close: () => {
|
||||
inputs.forEach(input => input.removeEventListener("input", updateTilesOptions));
|
||||
debug.selectAll("*").remove();
|
||||
clearInterval(loading);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("saveTilesScreen")
|
||||
.querySelectorAll("input")
|
||||
.forEach(el => el.addEventListener("input", updateTilesOptions));
|
||||
|
||||
function updateTilesOptions() {
|
||||
if (this?.tagName === "INPUT") {
|
||||
const {nextElementSibling: next, previousElementSibling: prev} = this;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ function editProvinces() {
|
|||
document.getElementById("provincesManually").addEventListener("click", enterProvincesManualAssignent);
|
||||
document.getElementById("provincesManuallyApply").addEventListener("click", applyProvincesManualAssignent);
|
||||
document.getElementById("provincesManuallyCancel").addEventListener("click", () => exitProvincesManualAssignment());
|
||||
document.getElementById("provincesRelease").addEventListener("click", triggerProvincesRelease);
|
||||
document.getElementById("provincesAdd").addEventListener("click", enterAddProvinceMode);
|
||||
document.getElementById("provincesRecolor").addEventListener("click", recolorProvinces);
|
||||
|
||||
|
|
@ -129,19 +130,27 @@ function editProvinces() {
|
|||
const separable = p.burg && p.burg !== pack.states[p.state].capital;
|
||||
const focused = defs.select("#fog #focusProvince" + p.i).size();
|
||||
COArenderer.trigger("provinceCOA" + p.i, p.coa);
|
||||
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${p.color}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${p.color}" class="fillRect pointer"></svg>
|
||||
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${
|
||||
p.color
|
||||
}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
p.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly>
|
||||
<svg data-tip="Click to show and edit province emblem" class="coaIcon hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
|
||||
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly>
|
||||
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span>
|
||||
<select data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital" class="cultureBase hide ${p.burgs.length ? "" : "placeholder"}">${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""}</select>
|
||||
<select data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital" class="cultureBase hide ${
|
||||
p.burgs.length ? "" : "placeholder"
|
||||
}">${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""}</select>
|
||||
<input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled">
|
||||
<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>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Declare province independence (turn non-capital province with burgs into a new state)" class="icon-flag-empty ${separable ? "" : "placeholder"} hide"></span>
|
||||
<span data-tip="Declare province independence (turn non-capital province with burgs into a new state)" class="icon-flag-empty ${
|
||||
separable ? "" : "placeholder"
|
||||
} hide"></span>
|
||||
<span data-tip="Toggle province focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
|
||||
<span data-tip="Remove the province" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
|
|
@ -222,74 +231,64 @@ function editProvinces() {
|
|||
function capitalZoomIn(p) {
|
||||
const capital = pack.provinces[p].burg;
|
||||
const l = burgLabels.select("[data-id='" + capital + "']");
|
||||
const x = +l.attr("x"),
|
||||
y = +l.attr("y");
|
||||
const x = +l.attr("x");
|
||||
const y = +l.attr("y");
|
||||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function triggerIndependencePromps(p) {
|
||||
alertMessage.innerHTML = "Are you sure you want to declare province independence? <br>It will turn province into a new state";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
confirmationDialog({
|
||||
title: "Declare independence",
|
||||
buttons: {
|
||||
Declare: function () {
|
||||
declareProvinceIndependence(p);
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
message: "Are you sure you want to declare province independence? <br>It will turn province into a new state",
|
||||
confirm: "Declare",
|
||||
onConfirm: () => {
|
||||
const [oldStateId, newStateId] = declareProvinceIndependence(p);
|
||||
updateStatesPostRelease([oldStateId], [newStateId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function declareProvinceIndependence(p) {
|
||||
const states = pack.states,
|
||||
provinces = pack.provinces,
|
||||
cells = pack.cells;
|
||||
if (provinces[p].burgs.some(b => pack.burgs[b].capital)) {
|
||||
tip("Cannot declare independence of a province having capital burg. Please change capital first", false, "error");
|
||||
return;
|
||||
}
|
||||
function declareProvinceIndependence(provinceId) {
|
||||
const {states, provinces, cells, burgs} = pack;
|
||||
const province = provinces[provinceId];
|
||||
const {name, burg: burgId, burgs: provinceBurgs} = province;
|
||||
|
||||
const oldState = pack.provinces[p].state;
|
||||
const newState = pack.states.length;
|
||||
if (provinceBurgs.some(b => burgs[b].capital))
|
||||
return tip("Cannot declare independence of a province having capital burg. Please change capital first", false, "error");
|
||||
if (!burgId) return tip("Cannot declare independence of a province without burg", false, "error");
|
||||
|
||||
const oldStateId = province.state;
|
||||
const newStateId = states.length;
|
||||
|
||||
// turn province burg into a capital
|
||||
const burg = provinces[p].burg;
|
||||
if (!burg) return;
|
||||
pack.burgs[burg].capital = 1;
|
||||
moveBurgToGroup(burg, "cities");
|
||||
burgs[burgId].capital = 1;
|
||||
moveBurgToGroup(burgId, "cities");
|
||||
|
||||
// move all burgs to a new state
|
||||
provinces[p].burgs.forEach(b => (pack.burgs[b].state = newState));
|
||||
province.burgs.forEach(b => (burgs[b].state = newStateId));
|
||||
|
||||
// difine new state attributes
|
||||
const center = pack.burgs[burg].cell;
|
||||
const culture = pack.burgs[burg].culture;
|
||||
const name = provinces[p].name;
|
||||
const {cell: center, culture} = burgs[burgId];
|
||||
const color = getRandomColor();
|
||||
|
||||
const coa = provinces[p].coa;
|
||||
const coaEl = document.getElementById("provinceCOA" + p);
|
||||
if (coaEl) coaEl.id = "stateCOA" + newState;
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
const coa = province.coa;
|
||||
const coaEl = document.getElementById("provinceCOA" + provinceId);
|
||||
if (coaEl) coaEl.id = "stateCOA" + newStateId;
|
||||
emblems.select(`#provinceEmblems > use[data-i='${provinceId}']`).remove();
|
||||
|
||||
// update cells
|
||||
cells.i
|
||||
.filter(i => cells.province[i] === p)
|
||||
.filter(i => cells.province[i] === provinceId)
|
||||
.forEach(i => {
|
||||
cells.province[i] = 0;
|
||||
cells.state[i] = newState;
|
||||
cells.state[i] = newStateId;
|
||||
});
|
||||
|
||||
// update diplomacy and reverse relations
|
||||
const diplomacy = states.map(s => {
|
||||
if (!s.i || s.removed) return "x";
|
||||
let relations = states[oldState].diplomacy[s.i]; // relations between Nth state and old overlord
|
||||
if (s.i === oldState) relations = "Enemy";
|
||||
// new state is Enemy to its old overlord
|
||||
let relations = states[oldStateId].diplomacy[s.i]; // relations between Nth state and old overlord
|
||||
// new state is Enemy to its old owner
|
||||
if (s.i === oldStateId) relations = "Enemy";
|
||||
else if (relations === "Ally") relations = "Suspicion";
|
||||
else if (relations === "Friendly") relations = "Suspicion";
|
||||
else if (relations === "Suspicion") relations = "Neutral";
|
||||
|
|
@ -301,28 +300,51 @@ function editProvinces() {
|
|||
return relations;
|
||||
});
|
||||
diplomacy.push("x");
|
||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]);
|
||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldStateId].name}`]);
|
||||
|
||||
// create new state
|
||||
states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: "Generic", center, culture, military: [], alert: 1, coa});
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms([newState]);
|
||||
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
else drawStates();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||
else drawBorders();
|
||||
BurgsAndStates.drawStateLabels([newState, oldState]);
|
||||
states.push({
|
||||
i: newStateId,
|
||||
name,
|
||||
diplomacy,
|
||||
provinces: [],
|
||||
color,
|
||||
expansionism: 0.5,
|
||||
capital: burgId,
|
||||
type: "Generic",
|
||||
center,
|
||||
culture,
|
||||
military: [],
|
||||
alert: 1,
|
||||
coa
|
||||
});
|
||||
|
||||
// remove old province
|
||||
unfog("focusProvince" + p);
|
||||
if (states[oldState].provinces.includes(p)) states[oldState].provinces.splice(states[oldState].provinces.indexOf(p), 1);
|
||||
provinces[p] = {i: p, removed: true};
|
||||
states[oldStateId].provinces = states[oldStateId].provinces.filter(p => p !== provinceId);
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
|
||||
// draw emblem
|
||||
COArenderer.add("state", newState, coa, pack.states[newState].pole[0], pack.states[newState].pole[1]);
|
||||
return [oldStateId, newStateId];
|
||||
}
|
||||
|
||||
function updateStatesPostRelease(oldStates, newStates) {
|
||||
const allStates = unique([...oldStates, ...newStates]);
|
||||
|
||||
layerIsOn("toggleProvinces") && toggleProvinces();
|
||||
layerIsOn("toggleStates") ? drawStates() : toggleStates();
|
||||
layerIsOn("toggleBorders") ? drawBorders() : toggleBorders();
|
||||
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms(newStates);
|
||||
BurgsAndStates.drawStateLabels(allStates);
|
||||
|
||||
// redraw emblems
|
||||
allStates.forEach(stateId => {
|
||||
emblems.select(`#stateEmblems > use[data-i='${stateId}']`)?.remove();
|
||||
const {coa, pole} = pack.states[stateId];
|
||||
COArenderer.add("state", stateId, coa, ...pole);
|
||||
});
|
||||
|
||||
unfog();
|
||||
closeDialogs();
|
||||
editStates();
|
||||
}
|
||||
|
|
@ -541,7 +563,17 @@ function editProvinces() {
|
|||
const provinces = pack.provinces
|
||||
.filter(p => p.i && !p.removed)
|
||||
.map(p => {
|
||||
return {id: p.i + states.length - 1, i: p.i, state: p.state, color: p.color, name: p.name, fullName: p.fullName, area: p.area, urban: p.urban, rural: p.rural};
|
||||
return {
|
||||
id: p.i + states.length - 1,
|
||||
i: p.i,
|
||||
state: p.state,
|
||||
color: p.color,
|
||||
name: p.name,
|
||||
fullName: p.fullName,
|
||||
area: p.area,
|
||||
urban: p.urban,
|
||||
rural: p.rural
|
||||
};
|
||||
});
|
||||
const data = states.concat(provinces);
|
||||
const root = d3
|
||||
|
|
@ -564,7 +596,13 @@ function editProvinces() {
|
|||
<option value="urban">Urban population</option>
|
||||
</select>`;
|
||||
alertMessage.innerHTML += `<div id='provinceInfo' class='chartInfo'>‍</div>`;
|
||||
const svg = d3.select("#alertMessage").insert("svg", "#provinceInfo").attr("id", "provincesTree").attr("width", width).attr("height", height).attr("font-size", "10px");
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#provinceInfo")
|
||||
.attr("id", "provincesTree")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("font-size", "10px");
|
||||
const graph = svg.append("g").attr("transform", `translate(10, 0)`);
|
||||
document.getElementById("provincesTreeType").addEventListener("change", updateChart);
|
||||
|
||||
|
|
@ -589,7 +627,14 @@ function editProvinces() {
|
|||
const rural = rn(d.data.rural * populationRate);
|
||||
const urban = rn(d.data.urban * populationRate * urbanization);
|
||||
|
||||
const value = provincesTreeType.value === "area" ? "Area: " + area : provincesTreeType.value === "rural" ? "Rural population: " + si(rural) : provincesTreeType.value === "urban" ? "Urban population: " + si(urban) : "Population: " + si(rural + urban);
|
||||
const value =
|
||||
provincesTreeType.value === "area"
|
||||
? "Area: " + area
|
||||
: provincesTreeType.value === "rural"
|
||||
? "Rural population: " + si(rural)
|
||||
: provincesTreeType.value === "urban"
|
||||
? "Urban population: " + si(urban)
|
||||
: "Population: " + si(rural + urban);
|
||||
|
||||
provinceInfo.innerHTML = `${name}. ${state}. ${value}`;
|
||||
provinceHighlightOn(ev);
|
||||
|
|
@ -637,7 +682,8 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function updateChart() {
|
||||
const value = this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : d => d.rural + d.urban;
|
||||
const value =
|
||||
this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : d => d.rural + d.urban;
|
||||
|
||||
root.sum(value);
|
||||
node.data(treeLayout(root).leaves());
|
||||
|
|
@ -681,6 +727,34 @@ function editProvinces() {
|
|||
provs.selectAll("text").call(d3.drag().on("drag", dragLabel)).classed("draggable", true);
|
||||
}
|
||||
|
||||
function triggerProvincesRelease() {
|
||||
confirmationDialog({
|
||||
title: "Release provinces",
|
||||
message: `Are you sure you want to release all provinces?
|
||||
</br>It will turn all separable provinces into independent states.
|
||||
</br>Capital province and provinces without any burgs will state as they are`,
|
||||
confirm: "Release",
|
||||
onConfirm: () => {
|
||||
const oldStateIds = [];
|
||||
const newStateIds = [];
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(el => {
|
||||
const provinceId = +el.dataset.id;
|
||||
const province = pack.provinces[provinceId];
|
||||
if (!province.burg) return;
|
||||
if (province.burg === pack.states[province.state].capital) return;
|
||||
if (province.burgs.some(burgId => pack.burgs[burgId].capital)) return;
|
||||
|
||||
const [oldStateId, newStateId] = declareProvinceIndependence(provinceId);
|
||||
oldStateIds.push(oldStateId);
|
||||
newStateIds.push(newStateId);
|
||||
});
|
||||
|
||||
updateStatesPostRelease(unique(oldStateIds), newStateIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enterProvincesManualAssignent() {
|
||||
if (!layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||
|
|
@ -783,7 +857,13 @@ function editProvinces() {
|
|||
if (pack.cells.province[i] === provinceNew) exists.remove();
|
||||
else exists.attr("data-province", provinceNew).attr("fill", fill);
|
||||
} else {
|
||||
temp.append("polygon").attr("points", getPackPolygon(i)).attr("data-cell", i).attr("data-province", provinceNew).attr("fill", fill).attr("stroke", "#555");
|
||||
temp
|
||||
.append("polygon")
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("data-cell", i)
|
||||
.attr("data-province", provinceNew)
|
||||
.attr("fill", fill)
|
||||
.attr("stroke", "#555");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -839,10 +919,8 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function enterAddProvinceMode() {
|
||||
if (this.classList.contains("pressed")) {
|
||||
exitAddProvinceMode();
|
||||
return;
|
||||
}
|
||||
if (this.classList.contains("pressed")) return exitAddProvinceMode();
|
||||
|
||||
customization = 12;
|
||||
this.classList.add("pressed");
|
||||
tip("Click on the map to place a new province center", true);
|
||||
|
|
@ -851,24 +929,17 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function addProvince() {
|
||||
const cells = pack.cells,
|
||||
provinces = pack.provinces;
|
||||
const {cells, provinces} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const center = findCell(point[0], point[1]);
|
||||
if (cells.h[center] < 20) {
|
||||
tip("You cannot place province into the water. Please click on a land cell", false, "error");
|
||||
return;
|
||||
}
|
||||
if (cells.h[center] < 20) return tip("You cannot place province into the water. Please click on a land cell", false, "error");
|
||||
|
||||
const oldProvince = cells.province[center];
|
||||
if (oldProvince && provinces[oldProvince].center === center) {
|
||||
tip("The cell is already a center of a different province. Select other cell", false, "error");
|
||||
return;
|
||||
}
|
||||
if (oldProvince && provinces[oldProvince].center === center)
|
||||
return tip("The cell is already a center of a different province. Select other cell", false, "error");
|
||||
|
||||
const state = cells.state[center];
|
||||
if (!state) {
|
||||
tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error");
|
||||
return;
|
||||
}
|
||||
if (!state) return tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error");
|
||||
|
||||
if (d3.event.shiftKey === false) exitAddProvinceMode();
|
||||
|
||||
|
|
@ -879,8 +950,8 @@ function editProvinces() {
|
|||
const name = burg ? pack.burgs[burg].name : Names.getState(Names.getCultureShort(c), c);
|
||||
const formName = oldProvince ? provinces[oldProvince].formName : "Province";
|
||||
const fullName = name + " " + formName;
|
||||
const stateColor = pack.states[state].color,
|
||||
rndColor = getRandomColor();
|
||||
const stateColor = pack.states[state].color;
|
||||
const rndColor = getRandomColor();
|
||||
const color = stateColor[0] === "#" ? d3.color(d3.interpolate(stateColor, rndColor)(0.2)).hex() : rndColor;
|
||||
|
||||
// generate emblem
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ function overviewRivers() {
|
|||
function zoomToRiver() {
|
||||
const r = +this.parentNode.dataset.id;
|
||||
const river = rivers.select("#river" + r).node();
|
||||
highlightElement(river);
|
||||
highlightElement(river, 3);
|
||||
}
|
||||
|
||||
function toggleBasinsHightlight() {
|
||||
|
|
|
|||
|
|
@ -126,9 +126,15 @@ function editStates() {
|
|||
|
||||
const capital = pack.burgs[s.capital].name;
|
||||
COArenderer.trigger("stateCOA" + s.i, s.coa);
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}" data-color="${s.color}" data-cells=${s.cells}
|
||||
data-area=${area} data-population=${population} data-burgs=${s.burgs} data-culture=${pack.cultures[s.culture].name} data-type=${s.type} data-expansionism=${s.expansionism}>
|
||||
<svg data-tip="State fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect pointer"></svg>
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}" data-color="${
|
||||
s.color
|
||||
}" data-cells=${s.cells}
|
||||
data-area=${area} data-population=${population} data-burgs=${s.burgs} data-culture=${pack.cultures[s.culture].name} data-type=${
|
||||
s.type
|
||||
} data-expansionism=${s.expansionism}>
|
||||
<svg data-tip="State fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
s.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
<input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly>
|
||||
<svg data-tip="Click to show and edit state emblem" class="coaIcon hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
|
||||
<input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly>
|
||||
|
|
@ -143,7 +149,9 @@ function editStates() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<select data-tip="State type. Defines growth model. Click to change" class="cultureType ${hidden} show hide">${getTypeOptions(s.type)}</select>
|
||||
<span data-tip="State expansionism" class="icon-resize-full ${hidden} show hide"></span>
|
||||
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value" class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${s.expansionism}>
|
||||
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value" class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${
|
||||
s.expansionism
|
||||
}>
|
||||
<span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div>
|
||||
<span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
|
||||
|
|
@ -200,7 +208,15 @@ function editStates() {
|
|||
if (customization || !state) return;
|
||||
const d = regions.select("#state" + state).attr("d");
|
||||
|
||||
const path = debug.append("path").attr("class", "highlight").attr("d", d).attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1).attr("filter", "url(#blur1)");
|
||||
const path = debug
|
||||
.append("path")
|
||||
.attr("class", "highlight")
|
||||
.attr("d", d)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
const l = path.node().getTotalLength(),
|
||||
dur = (l + 5000) / 2;
|
||||
|
|
@ -498,6 +514,7 @@ function editStates() {
|
|||
pack.cells.province.forEach((pr, i) => {
|
||||
if (pr === p) pack.cells.province[i] = 0;
|
||||
});
|
||||
|
||||
const coaId = "provinceCOA" + p;
|
||||
if (document.getElementById(coaId)) document.getElementById(coaId).remove();
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
|
|
@ -564,19 +581,20 @@ function editStates() {
|
|||
|
||||
function showStatesChart() {
|
||||
// build hierarchy tree
|
||||
const data = pack.states.filter(s => !s.removed);
|
||||
const statesData = pack.states.filter(s => !s.removed);
|
||||
if (statesData.length < 2) return tip("There are no states to show", false, "error");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.id(d => d.i)
|
||||
.parentId(d => (d.i ? 0 : null))(data)
|
||||
.parentId(d => (d.i ? 0 : null))(statesData)
|
||||
.sum(d => d.area)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const width = 150 + 200 * uiSizeOutput.value,
|
||||
height = 150 + 200 * uiSizeOutput.value;
|
||||
const size = 150 + 200 * uiSizeOutput.value;
|
||||
const margin = {top: 0, right: -50, bottom: 0, left: -50};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height - margin.top - margin.bottom;
|
||||
const w = size - margin.left - margin.right;
|
||||
const h = size - margin.top - margin.bottom;
|
||||
const treeLayout = d3.pack().size([w, h]).padding(3);
|
||||
|
||||
// prepare svg
|
||||
|
|
@ -588,7 +606,16 @@ function editStates() {
|
|||
<option value="burgs">Burgs number</option>
|
||||
</select>`;
|
||||
alertMessage.innerHTML += `<div id='statesInfo' class='chartInfo'>‍</div>`;
|
||||
const svg = d3.select("#alertMessage").insert("svg", "#statesInfo").attr("id", "statesTree").attr("width", width).attr("height", height).style("font-family", "Almendra SC").attr("text-anchor", "middle").attr("dominant-baseline", "central");
|
||||
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#statesInfo")
|
||||
.attr("id", "statesTree")
|
||||
.attr("width", size)
|
||||
.attr("height", size)
|
||||
.style("font-family", "Almendra SC")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "central");
|
||||
const graph = svg.append("g").attr("transform", `translate(-50, 0)`);
|
||||
document.getElementById("statesTreeType").addEventListener("change", updateChart);
|
||||
|
||||
|
|
@ -632,7 +659,16 @@ function editStates() {
|
|||
const urban = rn(d.data.urban * populationRate * urbanization);
|
||||
|
||||
const option = statesTreeType.value;
|
||||
const value = option === "area" ? "Area: " + area : option === "rural" ? "Rural population: " + si(rural) : option === "urban" ? "Urban population: " + si(urban) : option === "burgs" ? "Burgs number: " + d.data.burgs : "Population: " + si(rural + urban);
|
||||
const value =
|
||||
option === "area"
|
||||
? "Area: " + area
|
||||
: option === "rural"
|
||||
? "Rural population: " + si(rural)
|
||||
: option === "urban"
|
||||
? "Urban population: " + si(urban)
|
||||
: option === "burgs"
|
||||
? "Burgs number: " + d.data.burgs
|
||||
: "Population: " + si(rural + urban);
|
||||
|
||||
statesInfo.innerHTML = `${state}. ${value}`;
|
||||
stateHighlightOn(ev);
|
||||
|
|
@ -646,7 +682,16 @@ function editStates() {
|
|||
}
|
||||
|
||||
function updateChart() {
|
||||
const value = this.value === "area" ? d => d.area : this.value === "rural" ? d => d.rural : this.value === "urban" ? d => d.urban : this.value === "burgs" ? d => d.burgs : d => d.rural + d.urban;
|
||||
const value =
|
||||
this.value === "area"
|
||||
? d => d.area
|
||||
: this.value === "rural"
|
||||
? d => d.rural
|
||||
: this.value === "urban"
|
||||
? d => d.urban
|
||||
: this.value === "burgs"
|
||||
? d => d.burgs
|
||||
: d => d.rural + d.urban;
|
||||
|
||||
root.sum(value);
|
||||
node.data(treeLayout(root).leaves());
|
||||
|
|
@ -731,7 +776,11 @@ function editStates() {
|
|||
$("#statesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
|
||||
tip("Click on state to select, drag the circle to change state", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick).call(d3.drag().on("start", dragStateBrush)).on("touchmove mousemove", moveStateBrush);
|
||||
viewbox
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectStateOnMapClick)
|
||||
.call(d3.drag().on("start", dragStateBrush))
|
||||
.on("touchmove mousemove", moveStateBrush);
|
||||
|
||||
body.querySelector("div").classList.add("selected");
|
||||
}
|
||||
|
|
@ -797,9 +846,9 @@ function editStates() {
|
|||
}
|
||||
|
||||
function applyStatesManualAssignent() {
|
||||
const cells = pack.cells,
|
||||
affectedStates = [],
|
||||
affectedProvinces = [];
|
||||
const {cells} = pack;
|
||||
const affectedStates = [];
|
||||
const affectedProvinces = [];
|
||||
|
||||
statesBody
|
||||
.select("#temp")
|
||||
|
|
@ -815,77 +864,145 @@ function editStates() {
|
|||
|
||||
if (affectedStates.length) {
|
||||
refreshStatesEditor();
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
else drawStates();
|
||||
layerIsOn("toggleStates") ? drawStates() : toggleStates();
|
||||
if (adjustLabels.checked) BurgsAndStates.drawStateLabels([...new Set(affectedStates)]);
|
||||
adjustProvinces([...new Set(affectedProvinces)]);
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||
else drawBorders();
|
||||
layerIsOn("toggleBorders") ? drawBorders() : toggleBorders();
|
||||
if (layerIsOn("toggleProvinces")) drawProvinces();
|
||||
}
|
||||
|
||||
exitStatesManualAssignment();
|
||||
}
|
||||
|
||||
function adjustProvinces(affectedProvinces) {
|
||||
const {cells, provinces, states} = pack;
|
||||
const form = {Zone: 1, Area: 1, Territory: 2, Province: 1};
|
||||
const {cells, provinces, states, burgs} = pack;
|
||||
|
||||
affectedProvinces.forEach(p => {
|
||||
if (!p) return; // do nothing if neutral lands are captured
|
||||
const old = provinces[p].state;
|
||||
|
||||
// remove province from state provinces list
|
||||
if (states[old]?.provinces?.includes(p)) states[old].provinces.splice(states[old].provinces.indexOf(p), 1);
|
||||
affectedProvinces.forEach(provinceId => {
|
||||
if (!provinces[provinceId]) return; // lands without province captured => do nothing
|
||||
|
||||
// find states owning at least 1 province cell
|
||||
const provCells = cells.i.filter(i => cells.province[i] === p);
|
||||
const provCells = cells.i.filter(i => cells.province[i] === provinceId);
|
||||
const provStates = [...new Set(provCells.map(i => cells.state[i]))];
|
||||
|
||||
// assign province to its center owner; if center is neutral, remove province
|
||||
const owner = cells.state[provinces[p].center];
|
||||
if (owner) {
|
||||
const name = provinces[p].name;
|
||||
// province is captured completely => change owner or remove
|
||||
if (provinceId && provStates.length === 1) return changeProvinceOwner(provinceId, provStates[0], provCells);
|
||||
|
||||
// if province is a historical part of another state's province, unite with old province
|
||||
const part = states[owner].provinces.find(n => name.includes(provinces[n].name));
|
||||
if (part) {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter(i => cells.state[i] === owner).forEach(i => (cells.province[i] = part));
|
||||
} else {
|
||||
provinces[p].state = owner;
|
||||
states[owner].provinces.push(p);
|
||||
provinces[p].color = getMixedColor(states[owner].color);
|
||||
}
|
||||
} else {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter(i => !cells.state[i]).forEach(i => (cells.province[i] = 0));
|
||||
}
|
||||
|
||||
// create new provinces for non-main part
|
||||
provStates
|
||||
.filter(s => s && s !== owner)
|
||||
.forEach(s =>
|
||||
createProvince(
|
||||
p,
|
||||
s,
|
||||
provCells.filter(i => cells.state[i] === s)
|
||||
)
|
||||
);
|
||||
// province is captured partially => split province
|
||||
splitProvince(provinceId, provStates, provCells);
|
||||
});
|
||||
|
||||
function createProvince(initProv, state, provCells) {
|
||||
const province = provinces.length;
|
||||
provCells.forEach(i => (cells.province[i] = province));
|
||||
function changeProvinceOwner(provinceId, newOwnerId, provinceCells) {
|
||||
const province = provinces[provinceId];
|
||||
const prevOwner = states[province.state];
|
||||
|
||||
const burgCell = provCells.find(i => cells.burg[i]);
|
||||
const center = burgCell ? burgCell : provCells[0];
|
||||
const burg = burgCell ? cells.burg[burgCell] : 0;
|
||||
// remove province from old owner list
|
||||
prevOwner.provinces = prevOwner.provinces.filter(province => province !== provinceId);
|
||||
|
||||
const name = burgCell && P(0.7) ? getAdjective(pack.burgs[burg].name) : getAdjective(states[state].name) + " " + provinces[initProv].name.split(" ").slice(-1)[0];
|
||||
const formName = name.split(" ").length > 1 ? provinces[initProv].formName : rw(form);
|
||||
const fullName = name + " " + formName;
|
||||
const color = getMixedColor(states[state].color);
|
||||
provinces.push({i: province, state, center, burg, name, formName, fullName, color});
|
||||
if (newOwnerId) {
|
||||
// new owner is a state => change owner
|
||||
province.state = newOwnerId;
|
||||
states[newOwnerId].provinces.push(provinceId);
|
||||
} else {
|
||||
// new owner is neutral => remove province
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
provinceCells.forEach(i => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function splitProvince(provinceId, provinceStates, provinceCells) {
|
||||
const province = provinces[provinceId];
|
||||
const prevOwner = states[province.state];
|
||||
const provinceCenterOwner = cells.state[province.center];
|
||||
|
||||
provinceStates.forEach(stateId => {
|
||||
const stateProvinceCells = provinceCells.filter(i => cells.state[i] === stateId);
|
||||
|
||||
if (stateId === provinceCenterOwner) {
|
||||
// province center is owned by the same state => do nothing for this state
|
||||
if (stateId === prevOwner.i) return;
|
||||
|
||||
// province center is captured by neutrals => remove province
|
||||
if (!stateId) {
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
stateProvinceCells.forEach(i => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// reassign province ownership to province center owner
|
||||
prevOwner.provinces = prevOwner.provinces.filter(province => province !== provinceId);
|
||||
province.state = stateId;
|
||||
province.color = getMixedColor(states[stateId].color);
|
||||
states[stateId].provinces.push(provinceId);
|
||||
return;
|
||||
}
|
||||
|
||||
// province cells captured by neutrals => remove captured cells from province
|
||||
if (!stateId) {
|
||||
stateProvinceCells.forEach(i => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// a few province cells owned by state => add to closes province
|
||||
if (stateProvinceCells.length < 20) {
|
||||
const closestProvince = findClosestProvince(provinceId, stateId, stateProvinceCells);
|
||||
if (closestProvince) {
|
||||
stateProvinceCells.forEach(i => {
|
||||
cells.province[i] = closestProvince;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// some province cells owned by state => create new province
|
||||
createProvince(province, stateId, stateProvinceCells);
|
||||
});
|
||||
}
|
||||
|
||||
function createProvince(oldProvince, stateId, provinceCells) {
|
||||
const newProvinceId = provinces.length;
|
||||
const burgCell = provinceCells.find(i => cells.burg[i]);
|
||||
const center = burgCell ? burgCell : provinceCells[0];
|
||||
const burgId = burgCell ? cells.burg[burgCell] : 0;
|
||||
const burg = burgId ? burgs[burgId] : null;
|
||||
const culture = cells.culture[center];
|
||||
|
||||
const nameByBurg = burgCell && P(0.5);
|
||||
const name = nameByBurg ? burg.name : oldProvince.name || Names.getState(Names.getCultureShort(culture), culture);
|
||||
|
||||
const formOptions = ["Zone", "Area", "Territory", "Province"];
|
||||
const formName = burgCell && oldProvince.formName ? oldProvince.formName : ra(formOptions);
|
||||
|
||||
const color = getMixedColor(states[stateId].color);
|
||||
|
||||
const kinship = nameByBurg ? 0.8 : 0.4;
|
||||
const type = BurgsAndStates.getType(center, burg?.port);
|
||||
const coa = COA.generate(burg?.coa || states[stateId].coa, kinship, burg ? null : 0.9, type);
|
||||
coa.shield = COA.getShield(culture, stateId);
|
||||
|
||||
provinces.push({i: newProvinceId, state: stateId, center, burg: burgId, name, formName, fullName: `${name} ${formName}`, color, coa});
|
||||
|
||||
provinceCells.forEach(i => {
|
||||
cells.province[i] = newProvinceId;
|
||||
});
|
||||
|
||||
states[stateId].provinces.push(newProvinceId);
|
||||
}
|
||||
|
||||
function findClosestProvince(provinceId, stateId, sourceCells) {
|
||||
const borderCell = sourceCells.find(i =>
|
||||
cells.c[i].some(c => {
|
||||
return cells.state[c] === stateId && cells.province[c] && cells.province[c] !== provinceId;
|
||||
})
|
||||
);
|
||||
|
||||
const closesProvince = borderCell && cells.c[borderCell].map(c => cells.province[c]).find(province => province && province !== provinceId);
|
||||
return closesProvince;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -921,20 +1038,14 @@ function editStates() {
|
|||
}
|
||||
|
||||
function addState() {
|
||||
const states = pack.states,
|
||||
burgs = pack.burgs,
|
||||
cells = pack.cells;
|
||||
const {cells, states, burgs} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const center = findCell(point[0], point[1]);
|
||||
if (cells.h[center] < 20) {
|
||||
tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||
return;
|
||||
}
|
||||
if (cells.h[center] < 20) return tip("You cannot place state into the water. Please click on a land cell", false, "error");
|
||||
|
||||
let burg = cells.burg[center];
|
||||
if (burg && burgs[burg].capital) {
|
||||
tip("Existing capital cannot be selected as a new state capital! Select other cell", false, "error");
|
||||
return;
|
||||
}
|
||||
if (burg && burgs[burg].capital) return tip("Existing capital cannot be selected as a new state capital! Select other cell", false, "error");
|
||||
|
||||
if (!burg) burg = addBurg(point); // add new burg
|
||||
|
||||
const oldState = cells.state[center];
|
||||
|
|
@ -985,7 +1096,22 @@ function editStates() {
|
|||
cells.state[center] = newState;
|
||||
cells.province[center] = 0;
|
||||
|
||||
states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: "Generic", center, culture, military: [], alert: 1, coa, pole});
|
||||
states.push({
|
||||
i: newState,
|
||||
name,
|
||||
diplomacy,
|
||||
provinces: [],
|
||||
color,
|
||||
expansionism: 0.5,
|
||||
capital: burg,
|
||||
type: "Generic",
|
||||
center,
|
||||
culture,
|
||||
military: [],
|
||||
alert: 1,
|
||||
coa,
|
||||
pole
|
||||
});
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms([newState]);
|
||||
adjustProvinces([cells.province[center]]);
|
||||
|
|
@ -1028,7 +1154,8 @@ function editStates() {
|
|||
|
||||
function downloadStatesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,State,Full Name,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
|
||||
let data =
|
||||
"Id,State,Full Name,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
const key = parseInt(el.dataset.id);
|
||||
const statePack = pack.states[key];
|
||||
|
|
|
|||
|
|
@ -75,7 +75,11 @@ function selectStyleElement() {
|
|||
}
|
||||
|
||||
// stroke color and width
|
||||
if (["armies", "routes", "lakes", "borders", "cults", "relig", "cells", "coastline", "prec", "ice", "icons", "coordinates", "zones", "gridOverlay"].includes(sel)) {
|
||||
if (
|
||||
["armies", "routes", "lakes", "borders", "cults", "relig", "cells", "coastline", "prec", "ice", "icons", "coordinates", "zones", "gridOverlay"].includes(
|
||||
sel
|
||||
)
|
||||
) {
|
||||
styleStroke.style.display = "block";
|
||||
styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke");
|
||||
styleStrokeWidth.style.display = "block";
|
||||
|
|
@ -331,7 +335,6 @@ styleFilterInput.addEventListener("change", function () {
|
|||
|
||||
styleTextureInput.addEventListener("change", function () {
|
||||
if (this.value === "none") texture.select("image").attr("xlink:href", "");
|
||||
if (this.value === "default") texture.select("image").attr("xlink:href", getDefaultTexture());
|
||||
else getBase64(this.value, base64 => texture.select("image").attr("xlink:href", base64));
|
||||
});
|
||||
|
||||
|
|
@ -784,12 +787,42 @@ function applyDefaultStyle() {
|
|||
|
||||
biomes.attr("opacity", null).attr("filter", null).attr("mask", "url(#land)");
|
||||
ice.attr("opacity", 0.9).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)");
|
||||
stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null);
|
||||
provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "0 2").attr("stroke-linecap", "round").attr("filter", null);
|
||||
stateBorders
|
||||
.attr("opacity", 0.8)
|
||||
.attr("stroke", "#56566d")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("stroke-dasharray", "2")
|
||||
.attr("stroke-linecap", "butt")
|
||||
.attr("filter", null);
|
||||
provinceBorders
|
||||
.attr("opacity", 0.8)
|
||||
.attr("stroke", "#56566d")
|
||||
.attr("stroke-width", 0.5)
|
||||
.attr("stroke-dasharray", "0 2")
|
||||
.attr("stroke-linecap", "round")
|
||||
.attr("filter", null);
|
||||
cells.attr("opacity", null).attr("stroke", "#808080").attr("stroke-width", 0.1).attr("filter", null).attr("mask", null);
|
||||
|
||||
gridOverlay.attr("opacity", 0.8).attr("type", "pointyHex").attr("scale", 1).attr("dx", 0).attr("dy", 0).attr("stroke", "#777777").attr("stroke-width", 0.5).attr("stroke-dasharray", null).attr("filter", null).attr("mask", null);
|
||||
coordinates.attr("opacity", 1).attr("data-size", 12).attr("font-size", 12).attr("stroke", "#d4d4d4").attr("stroke-width", 1).attr("stroke-dasharray", 5).attr("filter", null).attr("mask", null);
|
||||
gridOverlay
|
||||
.attr("opacity", 0.8)
|
||||
.attr("type", "pointyHex")
|
||||
.attr("scale", 1)
|
||||
.attr("dx", 0)
|
||||
.attr("dy", 0)
|
||||
.attr("stroke", "#777777")
|
||||
.attr("stroke-width", 0.5)
|
||||
.attr("stroke-dasharray", null)
|
||||
.attr("filter", null)
|
||||
.attr("mask", null);
|
||||
coordinates
|
||||
.attr("opacity", 1)
|
||||
.attr("data-size", 12)
|
||||
.attr("font-size", 12)
|
||||
.attr("stroke", "#d4d4d4")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("stroke-dasharray", 5)
|
||||
.attr("filter", null)
|
||||
.attr("mask", null);
|
||||
compass.attr("opacity", 0.8).attr("transform", null).attr("filter", null).attr("mask", "url(#water)").attr("shape-rendering", "optimizespeed");
|
||||
if (!d3.select("#initial").size()) d3.select("#rose").attr("transform", "translate(80 80) scale(.25)");
|
||||
|
||||
|
|
@ -810,26 +843,68 @@ function applyDefaultStyle() {
|
|||
lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
|
||||
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null);
|
||||
|
||||
coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("auto-filter", 1).attr("filter", "url(#dropShadow)");
|
||||
coastline
|
||||
.select("#sea_island")
|
||||
.attr("opacity", 0.5)
|
||||
.attr("stroke", "#1f3846")
|
||||
.attr("stroke-width", 0.7)
|
||||
.attr("auto-filter", 1)
|
||||
.attr("filter", "url(#dropShadow)");
|
||||
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null);
|
||||
|
||||
terrain.attr("opacity", null).attr("set", "simple").attr("size", 1).attr("density", 0.4).attr("filter", null).attr("mask", null);
|
||||
rivers.attr("opacity", null).attr("fill", "#5d97bb").attr("filter", null);
|
||||
ruler.attr("opacity", null).attr("filter", null);
|
||||
|
||||
roads.attr("opacity", 0.9).attr("stroke", "#d06324").attr("stroke-width", 0.7).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null);
|
||||
trails.attr("opacity", 0.9).attr("stroke", "#d06324").attr("stroke-width", 0.25).attr("stroke-dasharray", ".8 1.6").attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null);
|
||||
searoutes.attr("opacity", 0.8).attr("stroke", "#ffffff").attr("stroke-width", 0.45).attr("stroke-dasharray", "1 2").attr("stroke-linecap", "round").attr("filter", null).attr("mask", null);
|
||||
roads
|
||||
.attr("opacity", 0.9)
|
||||
.attr("stroke", "#d06324")
|
||||
.attr("stroke-width", 0.7)
|
||||
.attr("stroke-dasharray", "2")
|
||||
.attr("stroke-linecap", "butt")
|
||||
.attr("filter", null)
|
||||
.attr("mask", null);
|
||||
trails
|
||||
.attr("opacity", 0.9)
|
||||
.attr("stroke", "#d06324")
|
||||
.attr("stroke-width", 0.25)
|
||||
.attr("stroke-dasharray", ".8 1.6")
|
||||
.attr("stroke-linecap", "butt")
|
||||
.attr("filter", null)
|
||||
.attr("mask", null);
|
||||
searoutes
|
||||
.attr("opacity", 0.8)
|
||||
.attr("stroke", "#ffffff")
|
||||
.attr("stroke-width", 0.45)
|
||||
.attr("stroke-dasharray", "1 2")
|
||||
.attr("stroke-linecap", "round")
|
||||
.attr("filter", null)
|
||||
.attr("mask", null);
|
||||
|
||||
statesBody.attr("opacity", 0.4).attr("filter", null);
|
||||
statesHalo.attr("data-width", 10).attr("stroke-width", 10).attr("opacity", 0.4).attr("filter", "blur(5px)");
|
||||
|
||||
provs.attr("opacity", 0.7).attr("fill", "#000000").attr("font-family", "Georgia").attr("data-size", 10).attr("font-size", 10).attr("filter", null);
|
||||
|
||||
temperature.attr("opacity", null).attr("fill", "#000000").attr("stroke-width", 1.8).attr("fill-opacity", 0.3).attr("font-size", "8px").attr("stroke-dasharray", null).attr("filter", null).attr("mask", null);
|
||||
temperature
|
||||
.attr("opacity", null)
|
||||
.attr("fill", "#000000")
|
||||
.attr("stroke-width", 1.8)
|
||||
.attr("fill-opacity", 0.3)
|
||||
.attr("font-size", "8px")
|
||||
.attr("stroke-dasharray", null)
|
||||
.attr("filter", null)
|
||||
.attr("mask", null);
|
||||
texture.attr("opacity", null).attr("filter", null).attr("mask", "url(#land)");
|
||||
texture.select("#textureImage").attr("x", 0).attr("y", 0);
|
||||
zones.attr("opacity", 0.6).attr("stroke", "#333333").attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt").attr("filter", null).attr("mask", null);
|
||||
zones
|
||||
.attr("opacity", 0.6)
|
||||
.attr("stroke", "#333333")
|
||||
.attr("stroke-width", 0)
|
||||
.attr("stroke-dasharray", null)
|
||||
.attr("stroke-linecap", "butt")
|
||||
.attr("filter", null)
|
||||
.attr("mask", null);
|
||||
|
||||
// ocean and svg default style
|
||||
svg.attr("background-color", "#000000").attr("data-filter", null).attr("filter", null);
|
||||
|
|
@ -838,24 +913,95 @@ function applyDefaultStyle() {
|
|||
svg.select("#oceanicPattern").attr("href", "./images/pattern1.png").attr("opacity", 0.2);
|
||||
|
||||
// heightmap style
|
||||
terrs.attr("opacity", null).attr("filter", null).attr("mask", "url(#land)").attr("stroke", "none").attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0);
|
||||
terrs
|
||||
.attr("opacity", null)
|
||||
.attr("filter", null)
|
||||
.attr("mask", "url(#land)")
|
||||
.attr("stroke", "none")
|
||||
.attr("scheme", "bright")
|
||||
.attr("terracing", 0)
|
||||
.attr("skip", 5)
|
||||
.attr("relax", 0)
|
||||
.attr("curve", 0);
|
||||
|
||||
// legend
|
||||
legend.attr("font-family", "Almendra SC").attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93).attr("data-columns", 8).attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round");
|
||||
legend
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", 13)
|
||||
.attr("data-size", 13)
|
||||
.attr("data-x", 99)
|
||||
.attr("data-y", 93)
|
||||
.attr("data-columns", 8)
|
||||
.attr("stroke-width", 2.5)
|
||||
.attr("stroke", "#812929")
|
||||
.attr("stroke-dasharray", "0 4 10 4")
|
||||
.attr("stroke-linecap", "round");
|
||||
legend.select("#legendBox").attr("fill", "#ffffff").attr("fill-opacity", 0.8);
|
||||
|
||||
const citiesSize = Math.max(rn(8 - regionsInput.value / 20), 3);
|
||||
burgLabels.select("#cities").attr("fill", "#3e3e4b").attr("opacity", 1).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("font-size", citiesSize).attr("data-size", citiesSize);
|
||||
burgIcons.select("#cities").attr("opacity", 1).attr("size", 1).attr("stroke-width", 0.24).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", 0.7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt");
|
||||
burgLabels
|
||||
.select("#cities")
|
||||
.attr("fill", "#3e3e4b")
|
||||
.attr("opacity", 1)
|
||||
.style("text-shadow", "white 0 0 4px")
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", citiesSize)
|
||||
.attr("data-size", citiesSize);
|
||||
burgIcons
|
||||
.select("#cities")
|
||||
.attr("opacity", 1)
|
||||
.attr("size", 1)
|
||||
.attr("stroke-width", 0.24)
|
||||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#3e3e4b")
|
||||
.attr("fill-opacity", 0.7)
|
||||
.attr("stroke-dasharray", "")
|
||||
.attr("stroke-linecap", "butt");
|
||||
anchors.select("#cities").attr("opacity", 1).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("stroke-width", 1.2).attr("size", 2);
|
||||
|
||||
burgLabels.select("#towns").attr("fill", "#3e3e4b").attr("opacity", 1).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("font-size", 3).attr("data-size", 4);
|
||||
burgIcons.select("#towns").attr("opacity", 1).attr("size", 0.5).attr("stroke-width", 0.12).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("fill-opacity", 0.7).attr("stroke-dasharray", "").attr("stroke-linecap", "butt");
|
||||
burgLabels
|
||||
.select("#towns")
|
||||
.attr("fill", "#3e3e4b")
|
||||
.attr("opacity", 1)
|
||||
.style("text-shadow", "white 0 0 4px")
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", 3)
|
||||
.attr("data-size", 4);
|
||||
burgIcons
|
||||
.select("#towns")
|
||||
.attr("opacity", 1)
|
||||
.attr("size", 0.5)
|
||||
.attr("stroke-width", 0.12)
|
||||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#3e3e4b")
|
||||
.attr("fill-opacity", 0.7)
|
||||
.attr("stroke-dasharray", "")
|
||||
.attr("stroke-linecap", "butt");
|
||||
anchors.select("#towns").attr("opacity", 1).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("stroke-width", 1.2).attr("size", 1);
|
||||
|
||||
const stateLabelSize = Math.max(rn(24 - regionsInput.value / 6), 6);
|
||||
labels.select("#states").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("font-size", stateLabelSize).attr("data-size", stateLabelSize).attr("filter", null);
|
||||
labels.select("#addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).style("text-shadow", "white 0 0 4px").attr("font-family", "Almendra SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null);
|
||||
labels
|
||||
.select("#states")
|
||||
.attr("fill", "#3e3e4b")
|
||||
.attr("opacity", 1)
|
||||
.attr("stroke", "#3a3a3a")
|
||||
.attr("stroke-width", 0)
|
||||
.style("text-shadow", "white 0 0 4px")
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", stateLabelSize)
|
||||
.attr("data-size", stateLabelSize)
|
||||
.attr("filter", null);
|
||||
labels
|
||||
.select("#addedLabels")
|
||||
.attr("fill", "#3e3e4b")
|
||||
.attr("opacity", 1)
|
||||
.attr("stroke", "#3a3a3a")
|
||||
.attr("stroke-width", 0)
|
||||
.style("text-shadow", "white 0 0 4px")
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", 18)
|
||||
.attr("data-size", 18)
|
||||
.attr("filter", null);
|
||||
|
||||
fogging.attr("opacity", 0.98).attr("fill", "#30426f");
|
||||
emblems.attr("opacity", 0.9).attr("stroke-width", 1).attr("filter", null);
|
||||
|
|
@ -887,40 +1033,49 @@ function applyStyle(style) {
|
|||
function changeStylePreset(preset) {
|
||||
if (customization) return tip("Please exit the customization mode first", false, "error");
|
||||
|
||||
alertMessage.innerHTML = "Are you sure you want to change the style preset? All unsaved style changes will be lost";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Change style preset",
|
||||
width: "23em",
|
||||
buttons: {
|
||||
Change: function () {
|
||||
const customPreset = localStorage.getItem(preset);
|
||||
if (customPreset) {
|
||||
if (JSON.isValid(customPreset)) applyStyle(JSON.parse(customPreset));
|
||||
else {
|
||||
tip("Cannot parse stored style JSON. Default style applied", false, "error", 5000);
|
||||
applyDefaultStyle();
|
||||
}
|
||||
} else if (defaultStyles[preset]) {
|
||||
const style = defaultStyles[preset];
|
||||
if (JSON.isValid(style)) applyStyle(JSON.parse(style));
|
||||
else tip("Cannot parse style JSON", false, "error", 5000);
|
||||
} else applyDefaultStyle();
|
||||
|
||||
removeStyleButton.style.display = stylePreset.selectedOptions[0].dataset.system ? "none" : "inline-block";
|
||||
updateElements(); // change elements
|
||||
selectStyleElement(); // re-select element to trigger values update
|
||||
updateMapFilter();
|
||||
localStorage.setItem("presetStyle", preset); // save preset to use it onload
|
||||
stylePreset.dataset.old = stylePreset.value; // save current value
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
stylePreset.value = stylePreset.dataset.old;
|
||||
$(this).dialog("close");
|
||||
if (sessionStorage.getItem("styleChangeWarningShown")) {
|
||||
changeStyle();
|
||||
} else {
|
||||
sessionStorage.setItem("styleChangeWarningShown", true);
|
||||
alertMessage.innerHTML = "Are you sure you want to change the style preset? All unsaved style changes will be lost";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Change style preset",
|
||||
width: "23em",
|
||||
buttons: {
|
||||
Change: function () {
|
||||
changeStyle();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
stylePreset.value = stylePreset.dataset.old;
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeStyle() {
|
||||
const customPreset = localStorage.getItem(preset);
|
||||
if (customPreset) {
|
||||
if (JSON.isValid(customPreset)) applyStyle(JSON.parse(customPreset));
|
||||
else {
|
||||
tip("Cannot parse stored style JSON. Default style applied", false, "error", 5000);
|
||||
applyDefaultStyle();
|
||||
}
|
||||
} else if (defaultStyles[preset]) {
|
||||
const style = defaultStyles[preset];
|
||||
if (JSON.isValid(style)) applyStyle(JSON.parse(style));
|
||||
else tip("Cannot parse style JSON", false, "error", 5000);
|
||||
} else applyDefaultStyle();
|
||||
|
||||
removeStyleButton.style.display = stylePreset.selectedOptions[0].dataset.system ? "none" : "inline-block";
|
||||
updateElements(); // change elements
|
||||
selectStyleElement(); // re-select element to trigger values update
|
||||
updateMapFilter();
|
||||
localStorage.setItem("presetStyle", preset); // save preset to use it onload
|
||||
stylePreset.dataset.old = stylePreset.value; // save current value
|
||||
}
|
||||
}
|
||||
|
||||
function updateElements() {
|
||||
|
|
@ -971,8 +1126,9 @@ function addStylePreset() {
|
|||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
|
||||
const currentStyle = document.getElementById("stylePreset").selectedOptions[0].text;
|
||||
document.getElementById("styleSaverName").value = currentStyle;
|
||||
const currentPreset = document.getElementById("stylePreset").selectedOptions[0];
|
||||
const styleName = currentPreset ? currentPreset.text : "custom";
|
||||
document.getElementById("styleSaverName").value = styleName;
|
||||
styleSaverJSON.value = JSON.stringify(getStyle(), null, 2);
|
||||
checkName();
|
||||
|
||||
|
|
@ -1092,6 +1248,11 @@ function addStylePreset() {
|
|||
applyOption(stylePreset, preset, styleSaverName.value); // add option
|
||||
localStorage.setItem("presetStyle", preset); // mark preset as default
|
||||
localStorage.setItem(preset, styleSaverJSON.value); // save preset
|
||||
|
||||
applyStyle(JSON.parse(styleSaverJSON.value));
|
||||
updateMapFilter();
|
||||
invokeActiveZooming();
|
||||
|
||||
$("#styleSaver").dialog("close");
|
||||
removeStyleButton.style.display = "inline-block";
|
||||
tip("Style preset is saved", false, "success", 4000);
|
||||
|
|
@ -1121,6 +1282,10 @@ function removeStylePreset() {
|
|||
localStorage.removeItem(stylePreset.value);
|
||||
stylePreset.selectedOptions[0].remove();
|
||||
removeStyleButton.style.display = "none";
|
||||
|
||||
applyDefaultStyle();
|
||||
updateMapFilter();
|
||||
invokeActiveZooming();
|
||||
}
|
||||
|
||||
// GLOBAL FILTERS
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
"use strict";
|
||||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
|
||||
toolsContent.addEventListener("click", function (event) {
|
||||
if (customization) {
|
||||
tip("Please exit the customization mode first", false, "warning");
|
||||
return;
|
||||
}
|
||||
if (event.target.tagName !== "BUTTON") return;
|
||||
if (!["BUTTON", "I"].includes(event.target.tagName)) return;
|
||||
const button = event.target.id;
|
||||
|
||||
// Click to open Editor buttons
|
||||
// click on open Editor buttons
|
||||
if (button === "editHeightmapButton") editHeightmap();
|
||||
else if (button === "editBiomesButton") editBiomes();
|
||||
else if (button === "editStatesButton") editStates();
|
||||
|
|
@ -25,9 +25,10 @@ toolsContent.addEventListener("click", function (event) {
|
|||
else if (button === "overviewBurgsButton") overviewBurgs();
|
||||
else if (button === "overviewRiversButton") overviewRivers();
|
||||
else if (button === "overviewMilitaryButton") overviewMilitary();
|
||||
else if (button === "overviewMarkersButton") overviewMarkers();
|
||||
else if (button === "overviewCellsButton") viewCellDetails();
|
||||
|
||||
// Click to Regenerate buttons
|
||||
// click on Regenerate buttons
|
||||
if (event.target.parentNode.id === "regenerateFeature") {
|
||||
if (sessionStorage.getItem("regenerateFeatureDontAsk")) {
|
||||
processFeatureRegeneration(event, button);
|
||||
|
|
@ -49,7 +50,9 @@ toolsContent.addEventListener("click", function (event) {
|
|||
},
|
||||
open: function () {
|
||||
const pane = $(this).dialog("widget").find(".ui-dialog-buttonpane");
|
||||
$('<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>').prependTo(pane);
|
||||
$(
|
||||
'<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>'
|
||||
).prependTo(pane);
|
||||
},
|
||||
close: function () {
|
||||
const box = $(this).dialog("widget").find(".checkbox")[0];
|
||||
|
|
@ -60,7 +63,10 @@ toolsContent.addEventListener("click", function (event) {
|
|||
});
|
||||
}
|
||||
|
||||
// Click to Add buttons
|
||||
// click on Configure regenerate buttons
|
||||
if (button === "configRegenerateMarkers") configMarkersGeneration();
|
||||
|
||||
// click on Add buttons
|
||||
if (button === "addLabel") toggleAddLabel();
|
||||
else if (button === "addBurgTool") toggleAddBurg();
|
||||
else if (button === "addRiver") toggleAddRiver();
|
||||
|
|
@ -88,7 +94,7 @@ function processFeatureRegeneration(event, button) {
|
|||
else if (button === "regenerateCultures") regenerateCultures();
|
||||
else if (button === "regenerateMilitary") regenerateMilitary();
|
||||
else if (button === "regenerateIce") regenerateIce();
|
||||
else if (button === "regenerateMarkers") regenerateMarkers(event);
|
||||
else if (button === "regenerateMarkers") regenerateMarkers();
|
||||
else if (button === "regenerateZones") regenerateZones(event);
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +268,8 @@ 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 burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) + states.length : +manorsInput.value + states.length;
|
||||
const burgsCount =
|
||||
manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) + states.length : +manorsInput.value + states.length;
|
||||
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between towns
|
||||
|
||||
//clear locked list since ids will change
|
||||
|
|
@ -413,23 +420,11 @@ function regenerateIce() {
|
|||
drawIce();
|
||||
}
|
||||
|
||||
function regenerateMarkers(event) {
|
||||
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default: 1, step: 0.01, min: 0, max: 100}, v => addNumberOfMarkers(v));
|
||||
else addNumberOfMarkers(gauss(1, 0.5, 0.3, 5, 2));
|
||||
|
||||
function addNumberOfMarkers(number) {
|
||||
// remove existing markers and assigned notes
|
||||
markers
|
||||
.selectAll("use")
|
||||
.each(function () {
|
||||
const index = notes.findIndex(n => n.id === this.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
})
|
||||
.remove();
|
||||
|
||||
addMarkers(number);
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
}
|
||||
function regenerateMarkers() {
|
||||
Markers.regenerate();
|
||||
turnButtonOn("toggleMarkers");
|
||||
drawMarkers();
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateZones(event) {
|
||||
|
|
@ -474,8 +469,23 @@ function addLabelOnClick() {
|
|||
const name = Names.getCulture(culture);
|
||||
const id = getNextId("label");
|
||||
|
||||
let group = labels.select("#addedLabels");
|
||||
if (!group.size()) group = labels.append("g").attr("id", "addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null);
|
||||
// use most recently selected label group
|
||||
const lastSelected = labelGroupSelect.value;
|
||||
const groupId = ["", "states", "burgLabels"].includes(lastSelected) ? "#addedLabels" : "#" + lastSelected;
|
||||
|
||||
let group = labels.select(groupId);
|
||||
if (!group.size())
|
||||
group = labels
|
||||
.append("g")
|
||||
.attr("id", "addedLabels")
|
||||
.attr("fill", "#3e3e4b")
|
||||
.attr("opacity", 1)
|
||||
.attr("stroke", "#3a3a3a")
|
||||
.attr("stroke-width", 0)
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", 18)
|
||||
.attr("data-size", 18)
|
||||
.attr("filter", null);
|
||||
|
||||
const example = group.append("text").attr("x", 0).attr("x", 0).text(name);
|
||||
const width = example.node().getBBox().width;
|
||||
|
|
@ -674,7 +684,7 @@ function addRouteOnClick() {
|
|||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
const pressed = document.getElementById("addMarker").classList.contains("pressed");
|
||||
const pressed = document.getElementById("addMarker")?.classList.contains("pressed");
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
|
|
@ -682,45 +692,115 @@ function toggleAddMarker() {
|
|||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addMarker.classList.add("pressed");
|
||||
closeDialogs(".stable");
|
||||
markersAddFromOverview.classList.add("pressed");
|
||||
|
||||
viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick);
|
||||
tip("Click on map to add a marker. Hold Shift to add multiple", true);
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
}
|
||||
|
||||
function addMarkerOnClick() {
|
||||
const {markers} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const x = rn(point[0], 2),
|
||||
y = rn(point[1], 2);
|
||||
const id = getNextId("markerElement");
|
||||
const x = rn(point[0], 2);
|
||||
const y = rn(point[1], 2);
|
||||
const i = markers.length ? last(markers).i + 1 : 0;
|
||||
|
||||
const selected = markerSelectGroup.value;
|
||||
const valid =
|
||||
selected &&
|
||||
d3
|
||||
.select("#defs-markers")
|
||||
.select("#" + selected)
|
||||
.size();
|
||||
const symbol = valid ? "#" + selected : "#marker0";
|
||||
const added = markers.select("[data-id='" + symbol + "']").size();
|
||||
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr("data-size") : 1;
|
||||
if (isNaN(desired)) desired = 1;
|
||||
const size = desired * 5 + 25 / scale;
|
||||
const isMarkerSelected = markers.length && elSelected?.node()?.parentElement?.id === "markers";
|
||||
const selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null;
|
||||
const baseMarker = selectedMarker || {icon: "❓"};
|
||||
const marker = {...baseMarker, i, x, y};
|
||||
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", symbol)
|
||||
.attr("data-id", symbol)
|
||||
.attr("data-x", x)
|
||||
.attr("data-y", y)
|
||||
.attr("x", x - size / 2)
|
||||
.attr("y", y - size)
|
||||
.attr("data-size", desired)
|
||||
.attr("width", size)
|
||||
.attr("height", size);
|
||||
markers.push(marker);
|
||||
const markersElement = document.getElementById("markers");
|
||||
const rescale = +markersElement.getAttribute("rescale");
|
||||
markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale));
|
||||
|
||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||
if (d3.event.shiftKey === false) {
|
||||
document.getElementById("markerAdd").classList.remove("pressed");
|
||||
document.getElementById("markersAddFromOverview").classList.remove("pressed");
|
||||
unpressClickToAddButton();
|
||||
}
|
||||
}
|
||||
|
||||
function configMarkersGeneration() {
|
||||
drawConfigTable();
|
||||
|
||||
function drawConfigTable() {
|
||||
const {markers} = pack;
|
||||
const config = Markers.getConfig();
|
||||
const headers = `<thead style='font-weight:bold'><tr>
|
||||
<td data-tip="Marker type name">Type</td>
|
||||
<td data-tip="Marker icon">Icon</td>
|
||||
<td data-tip="Marker number multiplier">Multiplier</td>
|
||||
<td data-tip="Number of markers of that type on the current map">Number</td>
|
||||
</tr></thead>`;
|
||||
const lines = config.map(({type, icon, multiplier}, index) => {
|
||||
const inputId = `markerIconInput${index}`;
|
||||
return `<tr>
|
||||
<td><input value="${type}" /></td>
|
||||
<td>
|
||||
<input id="${inputId}" style="width: 5em" value="${icon}" />
|
||||
<i class="icon-edit pointer" style="position: absolute; margin:.4em 0 0 -1.4em; font-size:.85em"></i>
|
||||
</td>
|
||||
<td><input type="number" min="0" max="100" step="0.1" value="${multiplier}" /></td>
|
||||
<td style="text-align:center">${markers.filter(marker => marker.type === type).length}</td>
|
||||
</tr>`;
|
||||
});
|
||||
const table = `<table class="table">${headers}<tbody>${lines.join("")}</tbody></table>`;
|
||||
alertMessage.innerHTML = table;
|
||||
|
||||
alertMessage.querySelectorAll("i").forEach(selectIconButton => {
|
||||
selectIconButton.addEventListener("click", function () {
|
||||
const input = this.previousElementSibling;
|
||||
selectIcon(input.value, icon => (input.value = icon));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const applyChanges = () => {
|
||||
const rows = alertMessage.querySelectorAll("tbody > tr");
|
||||
const rowsData = Array.from(rows).map(row => {
|
||||
const inputs = row.querySelectorAll("input");
|
||||
return {
|
||||
type: inputs[0].value,
|
||||
icon: inputs[1].value,
|
||||
multiplier: parseFloat(inputs[2].value)
|
||||
};
|
||||
});
|
||||
|
||||
const config = Markers.getConfig();
|
||||
const newConfig = config.map((markerType, index) => {
|
||||
const {type, icon, multiplier} = rowsData[index];
|
||||
return {...markerType, type, icon, multiplier};
|
||||
});
|
||||
|
||||
Markers.setConfig(newConfig);
|
||||
};
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Markers generation settings",
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
buttons: {
|
||||
Regenerate: () => {
|
||||
applyChanges();
|
||||
regenerateMarkers();
|
||||
drawConfigTable();
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () => tip("Apply changes and regenerate markers"));
|
||||
buttons[1].addEventListener("mousemove", () => tip("Close the window"));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog("destroy");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function viewCellDetails() {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ function editUnits() {
|
|||
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);
|
||||
|
||||
document.getElementById("addLinearRuler").addEventListener("click", addRuler);
|
||||
document.getElementById("addOpisometer").addEventListener("click", toggleOpisometerMode);
|
||||
|
|
@ -94,6 +96,10 @@ function editUnits() {
|
|||
urbanization = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanDensity() {
|
||||
urbanDensity = +this.value;
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
// distanceScale
|
||||
distanceScale = 3;
|
||||
|
|
@ -137,8 +143,10 @@ function editUnits() {
|
|||
// population
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
urbanization = urbanizationOutput.value = urbanizationInput.value = 1;
|
||||
urbanDensity = urbanDensityOutput.value = urbanDensityInput.value = 10;
|
||||
localStorage.removeItem("populationRate");
|
||||
localStorage.removeItem("urbanization");
|
||||
localStorage.removeItem("urbanDensity");
|
||||
}
|
||||
|
||||
function addRuler() {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ function editWorld() {
|
|||
buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
|
||||
buttons[4].addEventListener("mousemove", () => tip("Click to restore default wind directions"));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog("destroy");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue