").css({position:"absolute",width:e.outerWidth(),height:e.outerHeight()}).appendTo(e.parent()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(e){return t(e.target).closest(".ui-dialog").length?!0:!!t(e.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var e=!0;this._delay(function(){e=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(t){e||this._allowInteraction(t)||(t.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=t("
").appendTo(this._appendTo()),this._addClass(this.overlay,null,"ui-widget-overlay ui-front"),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var t=this.document.data("ui-dialog-overlays")-1;t?this.document.data("ui-dialog-overlays",t):(this._off(this.document,"focusin"),this.document.removeData("ui-dialog-overlays")),this.overlay.remove(),this.overlay=null}}}),t.uiBackCompat!==!1&&t.widget("ui.dialog",t.ui.dialog,{options:{dialogClass:""},_createWrapper:function(){this._super(),this.uiDialog.addClass(this.options.dialogClass)},_setOption:function(t,e){"dialogClass"===t&&this.uiDialog.removeClass(this.options.dialogClass).addClass(e),this._superApply(arguments)}}),t.ui.dialog});
\ No newline at end of file
diff --git a/libs/lineclip.js b/libs/lineclip.js
new file mode 100644
index 00000000..31a55d28
--- /dev/null
+++ b/libs/lineclip.js
@@ -0,0 +1,102 @@
+'use strict';
+// lineclip by mourner, https://github.com/mapbox/lineclip
+// Cohen-Sutherland line clippign algorithm, adapted to efficiently
+// handle polylines rather than just segments
+function lineclip(points, bbox, result) {
+ var len = points.length,
+ codeA = bitCode(points[0], bbox),
+ part = [],
+ i, a, b, codeB, lastCode;
+ if (!result) result = [];
+
+ for (i = 1; i < len; i++) {
+ a = points[i - 1];
+ b = points[i];
+ codeB = lastCode = bitCode(b, bbox);
+
+ while (true) {
+ if (!(codeA | codeB)) { // accept
+ part.push(a);
+
+ if (codeB !== lastCode) { // segment went outside
+ part.push(b);
+ if (i < len - 1) { // start a new line
+ result.push(part);
+ part = [];
+ }
+ } else if (i === len - 1) {
+ part.push(b);
+ }
+ break;
+
+ } else if (codeA & codeB) { // trivial reject
+ break;
+ } else if (codeA) { // a outside, intersect with clip edge
+ a = intersect(a, b, codeA, bbox);
+ codeA = bitCode(a, bbox);
+ } else { // b outside
+ b = intersect(a, b, codeB, bbox);
+ codeB = bitCode(b, bbox);
+ }
+ }
+ codeA = lastCode;
+ }
+
+ if (part.length) result.push(part);
+
+ return result;
+}
+
+// Sutherland-Hodgeman polygon clipping algorithm
+function polygonclip(points, bbox, secure = false) {
+ var result, edge, prev, prevInside, inter, i, p, inside;
+
+ // clip against each side of the clip rectangle
+ for (edge = 1; edge <= 8; edge *= 2) {
+ result = [];
+ prev = points[points.length - 1];
+ prevInside = !(bitCode(prev, bbox) & edge);
+
+ for (i = 0; i < points.length; i++) {
+ p = points[i];
+ inside = !(bitCode(p, bbox) & edge);
+ inter = inside !== prevInside; // segment goes through the clip window
+
+ if (secure && inter && inside && i) result.push(points[i-1]); // add previous point in secure mode (to get a correct d3 curve)
+ if (inter) result.push(intersect(prev, p, edge, bbox)); // add an intersection point
+ if (inside) result.push(p); // add a point if it's inside
+ else if (secure && prevInside) result.push(p); // // add an outside point if in secure mode (to get a correct d3 curve)
+
+ prev = p;
+ prevInside = inside;
+ }
+ points = result;
+ if (!points.length) break;
+ }
+ return result;
+}
+
+// intersect a segment against one of the 4 lines that make up the bbox
+function intersect(a, b, edge, bbox) {
+ return edge & 8 ? [a[0] + (b[0] - a[0]) * (bbox[3] - a[1]) / (b[1] - a[1]), bbox[3]] : // top
+ edge & 4 ? [a[0] + (b[0] - a[0]) * (bbox[1] - a[1]) / (b[1] - a[1]), bbox[1]] : // bottom
+ edge & 2 ? [bbox[2], a[1] + (b[1] - a[1]) * (bbox[2] - a[0]) / (b[0] - a[0])] : // right
+ edge & 1 ? [bbox[0], a[1] + (b[1] - a[1]) * (bbox[0] - a[0]) / (b[0] - a[0])] : null; // left
+}
+
+// bit code reflects the point position relative to the bbox:
+// left mid right
+// top 1001 1000 1010
+// mid 0001 0000 0010
+// bottom 0101 0100 0110
+function bitCode(p, bbox) {
+ var code = 0;
+
+ if (p[0] < bbox[0]) code |= 1; // left
+ else if (p[0] > bbox[2]) code |= 2; // right
+
+ if (p[1] < bbox[1]) code |= 4; // bottom
+ else if (p[1] > bbox[3]) code |= 8; // top
+
+ return code;
+}
\ No newline at end of file
diff --git a/main.js b/main.js
index 71fa053e..0626e5b7 100644
--- a/main.js
+++ b/main.js
@@ -7,7 +7,7 @@
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
"use strict";
-const version = "1.3"; // generator version
+const version = "1.4"; // generator version
document.title += " v" + version;
// if map version is not stored, clear localStorage and show a message
@@ -52,6 +52,7 @@ let trails = routes.append("g").attr("id", "trails");
let searoutes = routes.append("g").attr("id", "searoutes");
let temperature = viewbox.append("g").attr("id", "temperature");
let coastline = viewbox.append("g").attr("id", "coastline");
+let ice = viewbox.append("g").attr("id", "ice").style("display", "none");
let prec = viewbox.append("g").attr("id", "prec").style("display", "none");
let population = viewbox.append("g").attr("id", "population");
let labels = viewbox.append("g").attr("id", "labels");
@@ -220,7 +221,7 @@ function focusOn() {
const url = new URL(window.location.href);
const params = url.searchParams;
- if (params.get("from") === "MFCG") {
+ if (params.get("from") === "MFCG" && document.referrer) {
if (params.get("seed").length === 13) {
// show back burg from MFCG
params.set("burg", params.get("seed").slice(-4));
@@ -313,12 +314,12 @@ function applyDefaultBiomesSystem() {
const iconsDensity = [0,3,2,120,120,120,120,150,150,100,5,0,150];
const icons = [{},{dune:3, cactus:6, deadTree:1},{dune:9, deadTree:1},{acacia:1, grass:9},{grass:1},{acacia:8, palm:1},{deciduous:1},{acacia:5, palm:3, deciduous:1, swamp:1},{deciduous:6, swamp:1},{conifer:1},{grass:1},{},{swamp:1}];
const cost = [10,200,150,60,50,70,70,80,90,200,1000,5000,150]; // biome movement cost
- const biomesMartix = [ // hot ↔ cold; dry ↕ wet
- new Uint8Array([1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]),
- new Uint8Array([3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,9,9,9,9,9,10,10]),
+ const biomesMartix = [ // hot ↔ cold [>19°C; <-4°C]; dry ↕ wet
+ new Uint8Array([1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,10]),
+ new Uint8Array([3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,9,9,9,9,10,10,10]),
new Uint8Array([5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,9,9,9,9,9,10,10,10]),
new Uint8Array([5,6,6,6,6,6,6,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,10,10,10]),
- new Uint8Array([7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,10,10,10])
+ new Uint8Array([7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,10,10])
];
// parse icons weighted array into a simple array
@@ -527,7 +528,7 @@ function generate() {
elevateLakes();
Rivers.generate();
defineBiomes();
- //drawSeaIce();
+ drawIce();
rankCells();
Cultures.generate();
@@ -701,8 +702,9 @@ function openNearSeaLakes() {
// define map size and position based on template and random factor
function defineMapSize() {
const [size, latitude] = getSizeAndLatitude();
- if (!locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = size;
- if (!locked("latitude")) latitudeOutput.value = latitudeInput.value = latitude;
+ const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options
+ if (randomize || !locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = size;
+ if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = latitude;
function getSizeAndLatitude() {
const template = document.getElementById("templateInput").value; // heightmap template
@@ -963,7 +965,7 @@ function drawCoastline() {
let vchain = connectVertices(start, type);
if (features[f].type === "lake") relax(vchain, 1.2);
used[f] = 1;
- let points = vchain.map(v => vertices.p[v]);
+ let points = clipPoly(vchain.map(v => vertices.p[v]), 1);
const area = d3.polygonArea(points); // area with lakes/islands
if (area > 0 && features[f].type === "lake") {
points = points.reverse();
@@ -1056,7 +1058,6 @@ function reMarkFeatures() {
const start = queue[0]; // first cell
cells.f[start] = i; // assign feature number
const land = cells.h[start] >= 20;
- //const frozen = !land && temp[cells.g[start]] < -5; // check if water is frozen
let border = false; // true if feature touches map border
let cellNumber = 1; // to count cells number in a feature
@@ -1075,7 +1076,6 @@ function reMarkFeatures() {
else if (!cells.t[q] && cells.t[e] === 1) cells.t[q] = 2;
}
if (!cells.f[e] && land === eLand) {
- //if (!land && frozen !== temp[cells.g[e]] < -5) return;
queue.push(e);
cells.f[e] = i;
cellNumber++;
@@ -1137,28 +1137,32 @@ function elevateLakes() {
// assign biome id for each cell
function defineBiomes() {
console.time("defineBiomes");
- const cells = pack.cells, f = pack.features;
+ const cells = pack.cells, f = pack.features, temp = grid.cells.temp, prec = grid.cells.prec;
cells.biome = new Uint8Array(cells.i.length); // biomes array
for (const i of cells.i) {
- if (f[cells.f[i]].group === "freshwater") cells.h[i] = 19; // de-elevate lakes
- const temp = grid.cells.temp[cells.g[i]]; // temperature
+ if (f[cells.f[i]].group === "freshwater") cells.h[i] = 19; // de-elevate lakes; here to save some resources
+ const t = temp[cells.g[i]]; // cell temperature
+ const h = cells.h[i]; // cell height
+ const m = h < 20 ? 0 : calculateMoisture(i); // cell moisture
+ cells.biome[i] = getBiomeId(m, t, h);
+ }
- if (cells.h[i] < 20 && temp > -6) continue; // liquid water cells have biome 0
- let moist = grid.cells.prec[cells.g[i]];
+ function calculateMoisture(i) {
+ let moist = prec[cells.g[i]];
if (cells.r[i]) moist += Math.max(cells.fl[i] / 20, 2);
- const n = cells.c[i].filter(isLand).map(c => grid.cells.prec[cells.g[c]]).concat([moist]);
- moist = rn(4 + d3.mean(n));
- cells.biome[i] = getBiomeId(moist, temp, cells.h[i]);
+ const n = cells.c[i].filter(isLand).map(c => prec[cells.g[c]]).concat([moist]);
+ return rn(4 + d3.mean(n));
}
console.timeEnd("defineBiomes");
}
+// assign biome id to a cell
function getBiomeId(moisture, temperature, height) {
if (temperature < -5) return 11; // permafrost biome, including sea ice
- if (height < 20) return 0; // liquid water cells have marine biome
- if (moisture > 40 && height < 25 || moisture > 24 && height > 24) return 12; // wetland biome
+ if (height < 20) return 0; // marine biome: liquid water cells
+ if (moisture > 40 && temperature > -2 && (height < 25 || moisture > 24 && height > 24)) return 12; // wetland biome
const m = Math.min(moisture / 5 | 0, 4); // moisture band from 0 to 4
const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25
return biomesData.biomesMartix[m][t];
@@ -1211,20 +1215,15 @@ function addMarkers(number = 1) {
void function addVolcanoes() {
let mounts = Array.from(cells.i).filter(i => cells.h[i] > 70).sort((a, b) => cells.h[b] - cells.h[a]);
let count = mounts.length < 10 ? 0 : Math.ceil(mounts.length / 300 * number);
- if (count) addMarker("volcano", "🌋", 52, 52, 17.5);
+ if (count) addMarker("volcano", "🌋", 52, 50, 13);
while (count && mounts.length) {
const cell = mounts.splice(biased(0, mounts.length-1, 5), 1);
const x = cells.p[cell][0], y = cells.p[cell][1];
- const id = getNextId("markerElement");
- markers.append("use").attr("id", id).attr("data-cell", cell)
- .attr("xlink:href", "#marker_volcano").attr("data-id", "#marker_volcano")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
- const height = getFriendlyHeight([x, y]);
+ const id = appendMarker(cell, "volcano");
const proper = Names.getCulture(cells.culture[cell]);
const name = P(.3) ? "Mount " + proper : Math.random() > .3 ? proper + " Volcano" : proper;
- notes.push({id, name, legend:`Active volcano. Height: ${height}`});
+ notes.push({id, name, legend:`Active volcano. Height: ${getFriendlyHeight([x, y])}`});
count--;
}
}()
@@ -1232,17 +1231,11 @@ function addMarkers(number = 1) {
void function addHotSprings() {
let springs = Array.from(cells.i).filter(i => cells.h[i] > 50).sort((a, b) => cells.h[b]-cells.h[a]);
let count = springs.length < 30 ? 0 : Math.ceil(springs.length / 1000 * number);
- if (count) addMarker("hot_springs", "♨", 50, 50, 19.5);
+ if (count) addMarker("hot_springs", "♨️", 50, 52, 12.5);
while (count && springs.length) {
const cell = springs.splice(biased(1, springs.length-1, 3), 1);
- const x = cells.p[cell][0], y = cells.p[cell][1];
- const id = getNextId("markerElement");
- markers.append("use").attr("id", id)
- .attr("xlink:href", "#marker_hot_springs").attr("data-id", "#marker_hot_springs")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
-
+ const id = appendMarker(cell, "hot_springs");
const proper = Names.getCulture(cells.culture[cell]);
const temp = convertTemperature(gauss(30,15,20,100));
notes.push({id, name: proper + " Hot Springs", legend:`A hot springs area. Temperature: ${temp}`});
@@ -1255,17 +1248,12 @@ function addMarkers(number = 1) {
let count = !hills.length ? 0 : Math.ceil(hills.length / 7 * number);
if (!count) return;
- addMarker("mine", "⚒", 50, 50, 20);
+ addMarker("mine", "⛏️", 48, 50, 13.5);
const resources = {"salt":5, "gold":2, "silver":4, "copper":2, "iron":3, "lead":1, "tin":1};
while (count && hills.length) {
const cell = hills.splice(Math.floor(Math.random() * hills.length), 1);
- const x = cells.p[cell][0], y = cells.p[cell][1];
- const id = getNextId("markerElement");
- markers.append("use").attr("id", id)
- .attr("xlink:href", "#marker_mine").attr("data-id", "#marker_mine")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
+ const id = appendMarker(cell, "mine");
const resource = rw(resources);
const burg = pack.burgs[cells.burg[cell]];
const name = `${burg.name} — ${resource} mining town`;
@@ -1285,17 +1273,11 @@ function addMarkers(number = 1) {
.sort((a, b) => (cells.road[b] + cells.fl[b] / 10) - (cells.road[a] + cells.fl[a] / 10));
let count = !bridges.length ? 0 : Math.ceil(bridges.length / 12 * number);
- if (count) addMarker("bridge", "🌉", 50, 50, 16.5);
+ if (count) addMarker("bridge", "🌉", 50, 50, 14);
while (count && bridges.length) {
const cell = bridges.splice(0, 1);
- const x = cells.p[cell][0], y = cells.p[cell][1];
- const id = getNextId("markerElement");
- markers.append("use").attr("id", id)
- .attr("xlink:href", "#marker_bridge").attr("data-id", "#marker_bridge")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
-
+ const id = appendMarker(cell, "bridge");
const burg = pack.burgs[cells.burg[cell]];
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
const riverName = river ? `${river.name} ${river.type}` : "river";
@@ -1310,7 +1292,7 @@ function addMarkers(number = 1) {
let taverns = Array.from(cells.i).filter(i => cells.crossroad[i] && cells.h[i] >= 20 && cells.road[i] > maxRoad);
if (!taverns.length) return;
const count = Math.ceil(4 * number);
- addMarker("inn", "🍻", 50, 50, 17.5);
+ addMarker("inn", "🍻", 50, 50, 14.5);
const color = ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"];
const animal = ["Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Wolf", "Wolverine", "Camel", "Falcon", "Hound", "Ox"];
@@ -1318,14 +1300,7 @@ function addMarkers(number = 1) {
for (let i=0; i < taverns.length && i < count; i++) {
const cell = taverns.splice(Math.floor(Math.random() * taverns.length), 1);
- const x = cells.p[cell][0], y = cells.p[cell][1];
- const id = getNextId("markerElement");
-
- markers.append("use").attr("id", id)
- .attr("xlink:href", "#marker_inn").attr("data-id", "#marker_inn")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
-
+ const id = appendMarker(cell, "inn");
const type = P(.3) ? "inn" : "tavern";
const name = P(.5) ? ra(color) + " " + ra(animal) : P(.6) ? ra(adj) + " " + ra(animal) : ra(adj) + " " + capitalize(type);
notes.push({id, name: "The " + name, legend:`A big and famous roadside ${type}`});
@@ -1340,14 +1315,7 @@ function addMarkers(number = 1) {
for (let i=0; i < lighthouses.length && i < count; i++) {
const cell = lighthouses[i][0], vertex = lighthouses[i][1];
- const x = pack.vertices.p[vertex][0], y = pack.vertices.p[vertex][1];
- const id = getNextId("markerElement");
-
- markers.append("use").attr("id", id)
- .attr("xlink:href", "#marker_lighthouse").attr("data-id", "#marker_lighthouse")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
-
+ const id = appendMarker(cell, "lighthouse");
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend:`A lighthouse to keep the navigation safe`});
}
@@ -1360,14 +1328,7 @@ function addMarkers(number = 1) {
for (let i=0; i < waterfalls.length && i < count; i++) {
const cell = waterfalls[i];
- const x = cells.p[cell][0], y = cells.p[cell][1];
- const id = getNextId("markerElement");
-
- markers.append("use").attr("id", id)
- .attr("xlink:href", "#marker_waterfall").attr("data-id", "#marker_waterfall")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
-
+ const id = appendMarker(cell, "waterfall");
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend:`An extremely beautiful waterfall`});
}
@@ -1376,17 +1337,11 @@ function addMarkers(number = 1) {
void function addBattlefields() {
let battlefields = Array.from(cells.i).filter(i => cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25);
let count = battlefields.length < 100 ? 0 : Math.ceil(battlefields.length / 500 * number);
- if (count) addMarker("battlefield", "⚔", 50, 50, 20);
+ if (count) addMarker("battlefield", "⚔️", 50, 52, 12);
while (count && battlefields.length) {
const cell = battlefields.splice(Math.floor(Math.random() * battlefields.length), 1);
- const x = cells.p[cell][0], y = cells.p[cell][1];
- const id = getNextId("markerElement");
- markers.append("use").attr("id", id)
- .attr("xlink:href", "#marker_battlefield").attr("data-id", "#marker_battlefield")
- .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
- .attr("data-size", 1).attr("width", 30).attr("height", 30);
-
+ const id = appendMarker(cell, "battlefield");
const campaign = ra(states[cells.state[cell]].campaigns);
const date = generateDate(campaign.start, campaign.end);
const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
@@ -1407,6 +1362,19 @@ function addMarkers(number = 1) {
.attr("font-size", size+"px").attr("dominant-baseline", "central").text(icon);
}
+ function appendMarker(cell, type) {
+ const x = cells.p[cell][0], y = cells.p[cell][1];
+ const id = getNextId("markerElement");
+ const name = "#marker_" + type;
+
+ markers.append("use").attr("id", id)
+ .attr("xlink:href", name).attr("data-id", name)
+ .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
+ .attr("data-size", 1).attr("width", 30).attr("height", 30);
+
+ return id;
+ }
+
console.timeEnd("addMarkers");
}
@@ -1593,14 +1561,11 @@ function addZones(number = 1) {
}
function addEruption() {
- const volcanoes = [];
- markers.selectAll("use[data-id='#marker_volcano']").each(function() {
- volcanoes.push(this.dataset.cell);
- });
- if (!volcanoes.length) return;
+ const volcano = document.getElementById("markers").querySelector("use[data-id='#marker_volcano']");
+ if (!volcano) return;
- const cell = +ra(volcanoes);
- const id = markers.select("use[data-cell='"+cell+"']").attr("id");
+ const x = +volcano.dataset.x, y = +volcano.dataset.y, cell = findCell(x, y);
+ const id = volcano.id;
const note = notes.filter(n => n.id === id);
if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano");
@@ -1613,7 +1578,7 @@ function addZones(number = 1) {
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
- if (used[e]) return;
+ if (used[e] || cells.h[e] < 20) return;
used[e] = 1;
queue.push(e);
});
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index 96765b8c..8b77bdbc 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -187,7 +187,7 @@
} else b.port = 0;
// define burg population (keep urbanization at about 10% rate)
- b.population = rn(Math.max((cells.s[i] + cells.road[i]) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
+ b.population = rn(Math.max((cells.s[i] + cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population
if (b.port) {
@@ -420,7 +420,7 @@
const passableLake = features[cells.f[c]].type === "lake" && features[cells.f[c]].cells < maxLake;
if (cells.b[c] || (cells.state[c] !== state && !passableLake)) {hull.add(cells.v[q][d]); return;}
const nC = cells.c[c].filter(n => cells.state[n] === state);
- const intersected = intersect(nQ, nC).length
+ const intersected = common(nQ, nC).length
if (hull.size > 20 && !intersected && !passableLake) {hull.add(cells.v[q][d]); return;}
if (used[c]) return;
used[c] = 1;
@@ -810,7 +810,7 @@
});
const monarchy = ["Duchy", "Grand Duchy", "Principality", "Kingdom", "Empire"]; // per expansionism tier
- const republic = {Republic:70, Federation:2, Oligarchy:2, Tetrarchy:1, Triumvirate:1, Diarchy:1, "Trade Company":3}; // weighted random
+ const republic = {Republic:75, Federation:4, Oligarchy:2, Tetrarchy:1, Triumvirate:1, Diarchy:1, "Trade Company":4, Junta:1}; // weighted random
const union = {Union:3, League:4, Confederation:1, "United Kingdom":1, "United Republic":1, "United Provinces":2, Commonwealth:1, Heptarchy:1}; // weighted random
for (const s of states) {
@@ -870,11 +870,13 @@
if (s.form === "Union") return rw(union);
if (s.form === "Theocracy") {
- // default name is "Theocracy", some culture bases have special names
- if ([0, 1, 2, 3, 4, 6, 8, 9, 13, 15, 20].includes(base)) return "Diocese"; // Euporean
- if ([7, 5].includes(base)) return "Eparchy"; // Greek, Ruthenian
- if ([21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish
- if ([18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
+ // default name is "Theocracy"
+ if (P(.5) && [0, 1, 2, 3, 4, 6, 8, 9, 13, 15, 20].includes(base)) return "Diocese"; // Euporean
+ if (P(.9) && [7, 5].includes(base)) return "Eparchy"; // Greek, Ruthenian
+ if (P(.9) && [21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish
+ if (P(.8) && [18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
+ if (P(.02)) return "Thearchy"; // "Thearchy" in very rare case
+ if (P(.05)) return "See"; // "See" in rare case
return "Theocracy";
}
}
diff --git a/modules/military-generator.js b/modules/military-generator.js
index 594bd828..e6dc44b1 100644
--- a/modules/military-generator.js
+++ b/modules/military-generator.js
@@ -208,11 +208,11 @@
const getDefaultOptions = function() {
return [
- {name:"infantry", rural:.25, urban:.2, crew:1, type:"melee", separate:0},
- {name:"archers", rural:.12, urban:.2, crew:1, type:"ranged", separate:0},
- {name:"cavalry", rural:.12, urban:.03, crew:3, type:"mounted", separate:0},
- {name:"artillery", rural:0, urban:.03, crew:8, type:"machinery", separate:0},
- {name:"fleet", rural:0, urban:.015, crew:100, type:"naval", separate:1}
+ {icon: "⚔️", name:"infantry", rural:.25, urban:.2, crew:1, power:1, type:"melee", separate:0},
+ {icon: "🏹", name:"archers", rural:.12, urban:.2, crew:1, power:1, type:"ranged", separate:0},
+ {icon: "🐴", name:"cavalry", rural:.12, urban:.03, crew:3, power:4, type:"mounted", separate:0},
+ {icon: "💣", name:"artillery", rural:0, urban:.03, crew:8, power:12, type:"machinery", separate:0},
+ {icon: "🌊", name:"fleet", rural:0, urban:.015, crew:100, power:50, type:"naval", separate:1}
];
}
@@ -271,16 +271,11 @@
// get default regiment emblem
const getEmblem = function(r) {
if (r.n) return "🌊";
- if (!Object.values(r.u).length) return "🛡️";
- const mainUnit = Object.entries(r.u).sort((a,b) => b[1]-a[1])[0][0];
- const type = options.military.find(u => u.name === mainUnit).type;
- if (type === "ranged") return "🏹";
- if (type === "mounted") return "🐴";
- if (type === "machinery") return "💣";
- if (type === "armored") return "🐢";
- if (type === "aviation") return "🦅";
- if (type === "magical") return "🔮";
- else return "⚔️";
+ if (!Object.values(r.u).length) return "🔰"; // regiment without troops
+ if (cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital
+ const mainUnit = Object.entries(r.u).sort((a,b) => b[1]-a[1])[0][0]; // unit with more troops in regiment
+ const unit = options.military.find(u => u.name === mainUnit);
+ return unit.icon;
}
const generateNote = function(r, s) {
diff --git a/modules/ocean-layers.js b/modules/ocean-layers.js
index 79514147..94a18fd2 100644
--- a/modules/ocean-layers.js
+++ b/modules/ocean-layers.js
@@ -11,6 +11,7 @@
if (outline === "none") return;
console.time("drawOceanLayers");
+ lineGen.curve(d3.curveBasisClosed);
cells = grid.cells, pointsN = grid.cells.i.length, vertices = grid.vertices;
const limits = outline === "random" ? randomizeOutline() : outline.split(",").map(s => +s);
markupOcean(limits);
@@ -29,7 +30,8 @@
const chain = connectVertices(start, t); // vertices chain to form a path
const relaxation = 1 + t * -2; // select only n-th point
const relaxed = chain.filter((v, i) => i % relaxation === 0 || vertices.c[v].some(c => c >= pointsN));
- if (relaxed.length >= 3) chains.push([t, relaxed.map(v => vertices.p[v])]);
+ const points = clipPoly(relaxed.map(v => vertices.p[v]), 1);
+ if (relaxed.length >= 3) chains.push([t, points]);
}
for (const t of limits) {
@@ -57,8 +59,8 @@
return limits;
}
+ // Define grid ocean cells type based on distance form land
function markupOcean(limits) {
- // Define ocean cells type based on distance form land
for (let t = -2; t >= limits[0]-1; t--) {
for (let i = 0; i < pointsN; i++) {
if (cells.t[i] !== t+1) continue;
diff --git a/modules/save-and-load.js b/modules/save-and-load.js
index 5e769fdd..a725b5a9 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -296,6 +296,33 @@ async function saveMap() {
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
}
+// Send .map file to server [test function]
+async function sendToURL(URL = "http://localhost:8000/upload.php") {
+ if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
+ closeDialogs("#alert");
+
+ const blob = await getMapData();
+ const xhr = new XMLHttpRequest();
+ xhr.open("POST", URL, true);
+ //xhr.setRequestHeader("Content-Type", "blob"); // set request header
+ xhr.onload = e => console.log("onload", e.responseText);
+ xhr.onreadystatechange = () => {if (xhr.readyState === 4 && xhr.status === 200) tip(`File is sent to cloud storage`, true, "success", 7000);}
+ xhr.send(blob);
+}
+
+// Load .map file from server [test function]
+async function loadFromCloud() {
+ ldb.get("lastMap", blob => {
+ if (blob) {
+ loadMapPrompt(blob);
+ } else {
+ tip("No map stored. Save map to storage first", true, "error", 2000);
+ console.error("No map stored");
+ }
+ });
+ uploadMap(blob);
+}
+
function saveGeoJSON_Cells() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
const cells = pack.cells, v = pack.vertices;
@@ -617,6 +644,7 @@ function parseLoadedData(data) {
texture = viewbox.select("#texture");
terrs = viewbox.select("#terrs");
biomes = viewbox.select("#biomes");
+ ice = viewbox.select("#ice");
cells = viewbox.select("#cells");
gridOverlay = viewbox.select("#gridOverlay");
coordinates = viewbox.select("#coordinates");
@@ -953,12 +981,34 @@ function parseLoadedData(data) {
Military.generate();
}
- if (version < 1.35) {
+ if (version < 1.4) {
// v 1.35 added dry lakes
if (!lakes.select("#dry").size()) {
lakes.append("g").attr("id", "dry");
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", .7).attr("filter", null);
}
+
+ // v 1.4 added ice layer
+ ice = viewbox.insert("g", "#coastline").attr("id", "ice").style("display", "none");
+ ice.attr("opacity", null).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)");
+ drawIce();
+
+ // v 1.4 added icon and power attributes for units
+ for (const unit of options.military) {
+ if (!unit.icon) unit.icon = getUnitIcon(unit.type);
+ if (!unit.power) unit.power = unit.crew;
+ }
+
+ function getUnitIcon(type) {
+ if (type === "naval") return "🌊";
+ if (type === "ranged") return "🏹";
+ if (type === "mounted") return "🐴";
+ if (type === "machinery") return "💣";
+ if (type === "armored") return "🐢";
+ if (type === "aviation") return "🦅";
+ if (type === "magical") return "🔮";
+ else return "⚔️";
+ }
}
}()
diff --git a/modules/ui/battle-screen.js b/modules/ui/battle-screen.js
new file mode 100644
index 00000000..8f2627d1
--- /dev/null
+++ b/modules/ui/battle-screen.js
@@ -0,0 +1,50 @@
+"use strict";
+function showBattleScreen(attacker, defender) {
+ if (customization) return;
+ closeDialogs(".stable");
+
+ const battle = {name:"Battle", attackers:[attacker], defenders:[defender]};
+ const battleAttackers = document.getElementById("battleAttackers");
+ const battleDefenders = document.getElementById("battleDefenders");
+ addHeaders();
+ addRegiment(battleAttackers, attacker);
+ addRegiment(battleDefenders, defender);
+
+ $("#battleScreen").dialog({
+ title: battle.name, resizable: false, width: fitContent(), close: closeBattleScreen,
+ position: {my: "center", at: "center", of: "#map"}
+ });
+
+ if (modules.showBattleScreen) return;
+ modules.showBattleScreen = true;
+
+ // add listeners
+ //document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
+
+ function addHeaders() {
+ document.getElementById("battleScreen").querySelectorAll("th").forEach(el => el.remove());
+ const attackers = battleAttackers.querySelector("tr");
+ const defenders = battleDefenders.querySelector("tr");
+ let headers = "
| ";
+
+ for (const u of options.military) {
+ const label = capitalize(u.name.replace(/_/g, ' '));
+ headers += `
${u.icon} | `;
+ }
+
+ headers += "
Total | ";
+ attackers.insertAdjacentHTML("beforebegin", headers);
+ defenders.insertAdjacentHTML("beforebegin", headers);
+ }
+
+ function addRegiment(div, regiment) {
+ const reg = document.createElement("div");
+ reg.innerHTML = regiment.name;
+ div.append(reg);
+ }
+
+ function closeBattleScreen() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/modules/ui/biomes-editor.js b/modules/ui/biomes-editor.js
index 07d25364..9d8ef4fc 100644
--- a/modules/ui/biomes-editor.js
+++ b/modules/ui/biomes-editor.js
@@ -215,6 +215,8 @@ function editBiomes() {
function addCustomBiome() {
const b = biomesData, i = biomesData.i.length;
+ if (i > 254) {tip("Maximum number of biomes reached (255), data cleansing is required", false, "error"); return;}
+
b.i.push(i);
b.color.push(getRandomColor());
b.habitability.push(50);
diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js
index aa641091..f58dee11 100644
--- a/modules/ui/burg-editor.js
+++ b/modules/ui/burg-editor.js
@@ -294,13 +294,14 @@ function editBurg(id) {
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), 65), 6);
const s = burg.MFCG || defSeed;
const cell = burg.cell;
- const hub = +pack.cells.road[cell] > 50;
- const river = pack.cells.r[cell] ? 1 : 0;
+ const hub = +cells.road[cell] > 50;
+ const river = cells.r[cell] ? 1 : 0;
const coast = +burg.port;
const citadel = +burg.citadel;
@@ -309,8 +310,31 @@ function editBurg(id) {
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;
+ let norm = rn(normalize(deg, 0, 360) * 8) / 4;
+ if (norm === 2) norm = 0;
+ return "sea="+norm;
+ // debug.selectAll("*").remove();
+ // pack.burgs.filter(b => b.port).forEach(b => {
+ // var p1 = pack.cells.p[b.cell];
+ // var p2 = pack.cells.p[pack.cells.haven[b.cell]];
+ // var deg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI - 90;
+ // if (deg < 0) deg += 360;
+ // var norm = rn(normalize(deg, 0, 360) * 8) / 4;
+ // if (norm === 2) norm = 0;
+ // debug.append("line").attr("x1", p1[0]).attr("y1", p1[1]).attr("x2", p2[0]).attr("y2", p2[1]).attr("stroke", "red").attr("stroke-width", .2);
+ // debug.append("circle").attr("cx", b.x).attr("cy", b.y).attr("r", .4);
+ // debug.append("text").attr("x", b.x+1).attr("y", b.y).attr("font-size", 2).text(rn(norm, 2));
+ // });
+ }
+
const site = "http://fantasycities.watabou.ru/";
- const url = `${site}?name=${name}&size=${size}&seed=${s}&hub=${hub}&random=0&continuous=0&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}`;
+ const url = `${site}?name=${name}&size=${size}&seed=${s}&hub=${hub}&random=0&continuous=0&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
openURL(url);
}
}
diff --git a/modules/ui/editors.js b/modules/ui/editors.js
index a14cbd12..2d65bdbf 100644
--- a/modules/ui/editors.js
+++ b/modules/ui/editors.js
@@ -26,6 +26,7 @@ function clicked() {
else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
else if (grand.id === "burgLabels") editBurg();
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 === "coastline") editCoastline();
@@ -581,4 +582,43 @@ function highlightElement(element) {
let y = box.y + box.height / 2;
if (tr[1]) y += tr[1];
if (scale >= 2) zoomTo(x, y, scale, 1600);
+}
+
+function selectIcon(initial, callback) {
+ if (!callback) return;
+ $("#iconSelector").dialog();
+
+ const table = document.getElementById("iconTable");
+ const input = document.getElementById("iconInput");
+ input.value = initial;
+
+ if (!table.innerHTML) {
+ const icons = ["⚔️","🏹","🐴","💣","🌊","🎯","⚓","🔮","📯","⚒️","🛡️","👑","⚜️",
+ "☠️","🎆","🗡️","🔪","⛏️","🔥","🩸","💧","🐾","🎪","🏰","🏯","⛓️","❤️","💘","💜","📜","🔔",
+ "🔱","💎","🌈","🌠","✨","💥","☀️","🌙","⚡","❄️","♨️","🎲","🚨","🌉","🗻","🌋","🧱",
+ "⚖️","✂️","🎵","👗","🎻","🎨","🎭","⛲","💉","📖","📕","🎁","💍","⏳","🕸️","⚗️","☣️","☢️",
+ "🔰","🎖️","🚩","🏳️","🏴","💪","✊","👊","🤜","🤝","🙏","🧙","🧙♀️","💂","🤴","🧛","🧟","🧞","🧝","👼",
+ "👻","👺","👹","🦄","🐲","🐉","🐎","🦓","🐺","🦊","🐱","🐈","🦁","🐯","🐅","🐆","🐕","🦌","🐵","🐒","🦍",
+ "🦅","🕊️","🐓","🦇","🦜","🐦","🦉","🐮","🐄","🐂","🐃","🐷","🐖","🐗","🐏","🐑","🐐","🐫","🦒","🐘","🦏","🐭","🐁","🐀",
+ "🐹","🐰","🐇","🦔","🐸","🐊","🐢","🦎","🐍","🐳","🐬","🦈","🐠","🐙","🦑","🐌","🦋","🐜","🐝","🐞","🦗","🕷️","🦂","🦀",
+ "🌳","🌲","🎄","🌴","🍂","🍁","🌵","☘️","🍀","🌿","🌱","🌾","🍄","🌽","🌸","🌹","🌻",
+ "🍒","🍏","🍇","🍉","🍅","🍓","🥔","🥕","🥩","🍗","🍞","🍻","🍺","🍲","🍷"
+ ];
+
+ let row = "";
+ for (let i=0; i < icons.length; i++) {
+ if (i%17 === 0) row = table.insertRow(i/17|0);
+ const cell = row.insertCell(i%17);
+ cell.innerHTML = icons[i];
+ }
+ }
+
+ table.onclick = e => {if (e.target.tagName === "TD") {input.value = e.target.innerHTML; callback(input.value)}};
+ table.onmouseover = e => {if (e.target.tagName === "TD") tip(`Click to select ${e.target.innerHTML} icon`)};
+
+ $("#iconSelector").dialog({width: fitContent(), title: "Select Icon",
+ buttons: {
+ Apply: function() {callback(input.value||"⠀"); $(this).dialog("close")},
+ Close: function() {callback(initial); $(this).dialog("close")}}
+ });
}
\ No newline at end of file
diff --git a/modules/ui/general.js b/modules/ui/general.js
index 8dfb044d..449d7798 100644
--- a/modules/ui/general.js
+++ b/modules/ui/general.js
@@ -106,6 +106,7 @@ function showMapTooltip(point, e, i, g) {
if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;}
if (group === "coastline") {tip("Click to edit the coastline"); return;}
if (group === "zones") {tip(path[path.length-8].dataset.description); return;}
+ if (group === "ice") {tip("Click to edit the Ice"); return;}
// covering elements
if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else
@@ -403,6 +404,7 @@ document.addEventListener("keyup", event => {
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 === 76) toggleLabels(); // "L" to toggle Labels layer
else if (key === 73) toggleIcons(); // "I" to toggle Icons layer
diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js
index 0c93d7f2..7f3a9bfd 100644
--- a/modules/ui/heightmap-editor.js
+++ b/modules/ui/heightmap-editor.js
@@ -965,7 +965,7 @@ function editHeightmap() {
closeDialogs("#imageConverter");
$("#imageConverter").dialog({
- title: "Image Converter", minHeight: "auto", width: "19.5em", resizable: false,
+ title: "Image Converter", maxHeight: svgHeight*.75, minHeight: "auto", width: "19.5em", resizable: false,
position: {my: "right top", at: "right-10 top+10", of: "svg"},
beforeClose: closeImageConverter
});
diff --git a/modules/ui/ice-editor.js b/modules/ui/ice-editor.js
new file mode 100644
index 00000000..f6648f48
--- /dev/null
+++ b/modules/ui/ice-editor.js
@@ -0,0 +1,105 @@
+"use strict";
+function editIce() {
+ if (customization) return;
+ closeDialogs(".stable");
+ if (!layerIsOn("toggleIce")) toggleIce();
+
+ elSelected = d3.select(d3.event.target);
+ const type = elSelected.attr("type") ? "Glacier" : "Iceberg";
+ document.getElementById("iceRandomize").style.display = type === "Glacier" ? "none" : "inline-block";
+ document.getElementById("iceSize").style.display = type === "Glacier" ? "none" : "inline-block";
+ if (type === "Iceberg") document.getElementById("iceSize").value = +elSelected.attr("size");
+ ice.selectAll("*").classed("draggable", true).call(d3.drag().on("drag", dragElement));
+
+ $("#iceEditor").dialog({
+ title: "Edit "+type, resizable: false,
+ position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"},
+ close: closeEditor
+ });
+
+ if (modules.editIce) return;
+ modules.editIce = true;
+
+ // add listeners
+ document.getElementById("iceEditStyle").addEventListener("click", () => editStyle("ice"));
+ document.getElementById("iceRandomize").addEventListener("click", randomizeShape);
+ document.getElementById("iceSize").addEventListener("input", changeSize);
+ document.getElementById("iceNew").addEventListener("click", toggleAdd);
+ document.getElementById("iceRemove").addEventListener("click", removeIce);
+
+ function randomizeShape() {
+ const c = grid.points[+elSelected.attr("cell")];
+ const s = +elSelected.attr("size");
+ const i = ra(grid.cells.i), cn = grid.points[i];
+ const poly = getGridPolygon(i).map(p => [p[0]-cn[0], p[1]-cn[1]]);
+ const points = poly.map(p => [rn(c[0] + p[0] * s, 2), rn(c[1] + p[1] * s, 2)]);
+ elSelected.attr("points", points);
+ }
+
+ function changeSize() {
+ const c = grid.points[+elSelected.attr("cell")];
+ const s = +elSelected.attr("size");
+ const flat = elSelected.attr("points").split(",").map(el => +el);
+ const pairs = [];
+ while (flat.length) pairs.push(flat.splice(0,2));
+ const poly = pairs.map(p => [(p[0]-c[0]) / s, (p[1]-c[1]) / s]);
+ const size = +this.value;
+ const points = poly.map(p => [rn(c[0] + p[0] * size, 2), rn(c[1] + p[1] * size, 2)]);
+ elSelected.attr("points", points).attr("size", size);
+ }
+
+ function toggleAdd() {
+ document.getElementById("iceNew").classList.toggle("pressed");
+ if (document.getElementById("iceNew").classList.contains("pressed")) {
+ viewbox.style("cursor", "crosshair").on("click", addIcebergOnClick);
+ tip("Click on map to create an iceberg. Hold Shift to add multiple", true);
+ } else {
+ clearMainTip();
+ viewbox.on("click", clicked).style("cursor", "default");
+ }
+ }
+
+ function addIcebergOnClick() {
+ const point = d3.mouse(this);
+ const i = findGridCell(point[0], point[1]);
+ const c = grid.points[i];
+ const s = +document.getElementById("iceSize").value;
+
+ const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) / s)|0, (p[1] + (c[1]-p[1]) / s)|0]);
+ const iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", s);
+ iceberg.call(d3.drag().on("drag", dragElement));
+ if (d3.event.shiftKey === false) toggleAdd();
+ }
+
+ function removeIce() {
+ const type = elSelected.attr("type") ? "Glacier" : "Iceberg";
+ alertMessage.innerHTML = `Are you sure you want to remove the ${type}?`;
+ $("#alert").dialog({resizable: false, title: "Remove "+type,
+ buttons: {
+ Remove: function() {
+ $(this).dialog("close");
+ elSelected.remove();
+ $("#iceEditor").dialog("close");
+ },
+ Cancel: function() {$(this).dialog("close");}
+ }
+ });
+ }
+
+ function dragElement() {
+ const tr = parseTransform(this.getAttribute("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;
+ this.setAttribute("transform", `translate(${(dx+x)},${(dy+y)})`);
+ });
+ }
+
+ function closeEditor() {
+ ice.selectAll("*").classed("draggable", false).call(d3.drag().on("drag", null));
+ clearMainTip();
+ iceNew.classList.remove("pressed");
+ unselect();
+ }
+}
diff --git a/modules/ui/layers.js b/modules/ui/layers.js
index d7a3faed..9be802b0 100644
--- a/modules/ui/layers.js
+++ b/modules/ui/layers.js
@@ -32,10 +32,10 @@ function getDefaultPresets() {
"cultural": ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"religions": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"provinces": ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"],
- "biomes": ["toggleBiomes", "toggleRivers", "toggleScaleBar"],
+ "biomes": ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar"],
"heightmap": ["toggleHeight", "toggleRivers"],
"physical": ["toggleCoordinates", "toggleHeight", "toggleRivers", "toggleScaleBar"],
- "poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
+ "poi": ["toggleBorders", "toggleHeight", "toggleIce", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"military": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleMilitary", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"],
"landmass": ["toggleScaleBar"]
}
@@ -345,7 +345,7 @@ function drawBiomes() {
const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b));
const chain = connectVertices(edgeVerticle, b);
if (chain.length < 3) continue;
- const points = chain.map(v => vertices.p[v]);
+ const points = clipPoly(chain.map(v => vertices.p[v]), 1);
paths[b] += "M" + points.join("L") + "Z";
}
@@ -456,38 +456,77 @@ function drawCells() {
cells.append("path").attr("d", path);
}
-function drawSeaIce() {
- const seaIce = viewbox.append("g").attr("id", "seaIce").attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("filter", "url(#dropShadow05)");//.attr("opacity", .8);
- for (const i of grid.cells.i) {
- const t = grid.cells.temp[i] ;
- if (t > 2) continue;
- if (t > -5 && grid.cells.h[i] >= 20) continue;
- if (t < -5) drawpolygon(i);
- if (P(normalize(t, -5.5, 2.5))) continue; // t[-5; 2]
- const size = t < -14 ? 0 : t > -6 ? (7 + t) / 10 : (15 + t) / 100; // [0; 1], where 0 = full size, 1 = zero size
- resizePolygon(i, rn(size * (.2 + rand() * .9), 2));
+function toggleIce() {
+ if (!layerIsOn("toggleIce")) {
+ turnButtonOn("toggleIce");
+ $('#ice').fadeIn();
+ if (event && isCtrlClick(event)) editStyle("ice");
+ } else {
+ if (event && isCtrlClick(event)) {editStyle("ice"); return;}
+ $('#ice').fadeOut();
+ turnButtonOff("toggleIce");
}
+}
- // -9: .06
- // -8: .07
- // -7: .08
- // -6: .09
+function drawIce() {
+ const cells = grid.cells, vertices = grid.vertices, n = cells.i.length, temp = cells.temp, h = cells.h;
+ const used = new Uint8Array(cells.i.length);
+ Math.seedrandom(seed);
- // -5: .2
- // -4: .3
- // -3: .4
- // -2: .5
- // -1: .6
- // 0: .7
+ const shieldMin = -6; // min temp to form ice shield (glacier)
+ const icebergMax = 2; // max temp to form an iceberg
- function drawpolygon(i) {
- seaIce.append("polygon").attr("points", getGridPolygon(i));
+ for (const i of grid.cells.i) {
+ const t = temp[i];
+ if (t > icebergMax) continue; // too warm: no ice
+ if (t > shieldMin && h[i] >= 20) continue; // non-glacier land: no ice
+
+ if (t <= shieldMin) {
+ // very cold: ice shield
+ if (used[i]) continue; // already rendered
+ const onborder = cells.c[i].some(n => temp[n] > shieldMin);
+ if (!onborder) continue; // need to start from onborder cell
+ const vertex = cells.v[i].find(v => vertices.c[v].some(i => temp[i] > shieldMin));
+ const chain = connectVertices(vertex);
+ if (chain.length < 3) continue;
+ const points = clipPoly(chain.map(v => vertices.p[v]));
+ ice.append("polygon").attr("points", points).attr("type", "iceShield");
+ continue;
+ }
+
+ // mildly cold: iceberd
+ if (P(normalize(t, -7, 2.5))) continue; // t[-5; 2] cold: skip some cells
+ if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers
+ let size = (6.5 + t) / 10; // iceberg size: 0 = full size, 1 = zero size
+ if (cells.t[i] === -1) size *= 1.3; // coasline: smaller icebers
+ size = Math.min(size * (.4 + rand() * 1.2), .95); // randomize iceberd size
+ resizePolygon(i, size);
}
function resizePolygon(i, s) {
const c = grid.points[i];
- const points = getGridPolygon(i).map(p => [p[0] + (c[0]-p[0]) * s, p[1] + (c[1]-p[1]) * s]);
- seaIce.append("polygon").attr("points", points);
+ const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) * s)|0, (p[1] + (c[1]-p[1]) * s)|0]);
+ ice.append("polygon").attr("points", points).attr("cell", i).attr("size", rn(1-s, 2));
+ }
+
+ // connect vertices to chain
+ function connectVertices(start) {
+ const chain = []; // vertices chain to form a path
+ for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) {
+ const prev = last(chain); // previous vertex in chain
+ chain.push(current); // add current vertex to sequence
+ const c = vertices.c[current]; // cells adjacent to vertex
+ c.filter(c => temp[c] <= shieldMin).forEach(c => used[c] = 1);
+ const c0 = c[0] >= n || temp[c[0]] > shieldMin;
+ const c1 = c[1] >= n || temp[c[1]] > shieldMin;
+ const c2 = c[2] >= n || temp[c[2]] > shieldMin;
+ const v = vertices.v[current]; // neighboring vertices
+ if (v[0] !== prev && c0 !== c1) current = v[0];
+ else if (v[1] !== prev && c1 !== c2) current = v[1];
+ else if (v[2] !== prev && c0 !== c2) current = v[2];
+ if (current === chain[chain.length - 1]) {console.error("Next vertex is not found"); break;}
+ }
+ return chain;
}
}
diff --git a/modules/ui/markers-editor.js b/modules/ui/markers-editor.js
index 884c531e..f7cd33dd 100644
--- a/modules/ui/markers-editor.js
+++ b/modules/ui/markers-editor.js
@@ -27,7 +27,7 @@ function editMarker() {
document.getElementById("markerIconSize").addEventListener("input", changeIconSize);
document.getElementById("markerIconShiftX").addEventListener("input", changeIconShiftX);
document.getElementById("markerIconShiftY").addEventListener("input", changeIconShiftY);
- document.getElementById("markerIconCustom").addEventListener("input", applyCustomUnicodeIcon);
+ document.getElementById("markerIconSelect").addEventListener("click", selectMarkerIcon);
document.getElementById("markerStyle").addEventListener("click", toggleStyleSection);
document.getElementById("markerSize").addEventListener("input", changeMarkerSize);
@@ -73,13 +73,7 @@ function editMarker() {
markerIconFill.value = icon.attr("fill");
markerToggleBubble.className = symbol.select("circle").attr("opacity") === "0" ? "icon-info" : "icon-info-circled";
-
- const table = document.getElementById("markerIconTable");
- let selected = table.getElementsByClassName("selected");
- if (selected.length) selected[0].removeAttribute("class");
- selected = document.querySelectorAll("#markerIcon" + icon.text().codePointAt());
- if (selected.length) selected[0].className = "selected";
- markerIconCustom.value = selected.length ? "" : icon.text();
+ markerIconSelect.innerHTML = icon.text();
}
function toggleGroupSection() {
@@ -162,242 +156,20 @@ function editMarker() {
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";
- if (!markerIconTable.innerHTML) drawIconsList();
+ markerIconSelect.style.display = "inline-block";
}
}
- function drawIconsList() {
- const icons = [
- // emoticons in FF:
- ["2693", "⚓", "Anchor"],
- ["26EA", "⛪", "Church"],
- ["1F3EF", "🏯", "Japanese Castle"],
- ["1F3F0", "🏰", "Castle"],
- ["1F5FC", "🗼", "Tower"],
- ["1F3E0", "🏠", "House"],
- ["1F3AA", "🎪", "Tent"],
- ["1F3E8", "🏨", "Hotel"],
- ["1F4B0", "💰", "Money bag"],
- ["1F6A8", "🚨", "Revolving Light"],
- ["1F309", "🌉", "Bridge at Night"],
- ["1F5FB", "🗻", "Mountain"],
- ["1F30B", "🌋", "Volcano"],
- ["270A", "✊", "Raised Fist"],
- ["1F44A", "👊", "Oncoming Fist"],
- ["1F4AA", "💪", "Flexed Biceps"],
- ["1F47C", "👼", "Baby Angel"],
- ["1F40E", "🐎", "Horse"],
- ["1F434", "🐴", "Horse Face"],
- ["1F42E", "🐮", "Cow"],
- ["1F43A", "🐺", "Wolf Face"],
- ["1F435", "🐵", "Monkey face"],
- ["1F437", "🐷", "Pig face"],
- ["1F414", "🐔", "Chicken"],
- ["1F411", "🐑", "Ewe"],
- ["1F42B", "🐫", "Camel"],
- ["1F418", "🐘", "Elephant"],
- ["1F422", "🐢", "Turtle"],
- ["1F40C", "🐌", "Snail"],
- ["1F40D", "🐍", "Snake"],
- ["1F41D", "🐝", "Honeybee"],
- ["1F41C", "🐜", "Ant"],
- ["1F41B", "🐛", "Bug"],
- ["1F426", "🐦", "Bird"],
- ["1F438", "🐸", "Frog Face"],
- ["1F433", "🐳", "Whale"],
- ["1F42C", "🐬", "Dolphin"],
- ["1F420", "🐟", "Fish"],
- ["1F480", "💀", "Skull"],
- ["1F432", "🐲", "Dragon Head"],
- ["1F479", "👹", "Ogre"],
- ["1F47A", "👺", "Goblin"],
- ["1F47B", "👻", "Ghost"],
- ["1F47E", "👾", "Alien"],
- ["1F383", "🎃", "Jack-O-Lantern"],
- ["1F384", "🎄", "Christmas Tree"],
- ["1F334", "🌴", "Palm"],
- ["1F335", "🌵", "Cactus"],
- ["2618", "☘️", "Shamrock"],
- ["1F340", "🍀", "Four Leaf Clover"],
- ["1F341", "🍁", "Maple Leaf"],
- ["1F33F", "🌿", "Herb"],
- ["1F33E", "🌾", "Sheaf"],
- ["1F344", "🍄", "Mushroom"],
- ["1F374", "🍴", "Fork and knife"],
- ["1F372", "🍲", "Food"],
- ["1F35E", "🍞", "Bread"],
- ["1F357", "🍗", "Poultry leg"],
- ["1F347", "🍇", "Grapes"],
- ["1F34F", "🍏", "Apple"],
- ["1F352", "🍒", "Cherries"],
- ["1F36F", "🍯", "Honey pot"],
- ["1F37A", "🍺", "Beer"],
- ["1F37B", "🍻", "Beers"],
- ["1F377", "🍷", "Wine glass"],
- ["1F3BB", "🎻", "Violin"],
- ["1F3B8", "🎸", "Guitar"],
- ["1F52A", "🔪", "Knife"],
- ["1F52B", "🔫", "Pistol"],
- ["1F4A3", "💣", "Bomb"],
- ["1F4A5", "💥", "Collision"],
- ["1F4A8", "💨", "Dashing away"],
- ["1F301", "🌁", "Foggy"],
- ["2744", "❄️", "Snowflake"],
- ["26A1", "⚡", "Electricity"],
- ["1F320", "🌠", "Shooting star"],
- ["1F319", "🌙", "Crescent moon"],
- ["1F525", "🔥", "Fire"],
- ["1F4A7", "💧", "Droplet"],
- ["1F30A", "🌊", "Wave"],
- ["23F0", "⏰", "Alarm Clock"],
- ["231B", "⌛", "Hourglass"],
- ["1F3C6", "🏆", "Goblet"],
- ["26F2", "⛲", "Fountain"],
- ["26F5", "⛵", "Sailboat"],
- ["26FA", "⛺", "Campfire"],
- ["2764", "❤", "Red Heart"],
- ["1F498", "💘", "Heart With Arrow"],
- ["1F489", "💉", "Syringe"],
- ["1F4D5", "📕", "Closed Book"],
- ["1F4D6", "📚", "Books"],
- ["1F381", "🎁", "Wrapped Gift"],
- ["1F3AF", "🎯", "Archery"],
- ["1F52E", "🔮", "Magic ball"],
- ["1F3AD", "🎭", "Performing arts"],
- ["1F3A8", "🎨", "Artist palette"],
- ["1F457", "👗", "Dress"],
- ["1F392", "🎒", "Backpack"],
- ["1F451", "👑", "Crown"],
- ["1F48D", "💍", "Ring"],
- ["1F48E", "💎", "Gem"],
- ["1F514", "🔔", "Bell"],
- ["1F3B2", "🎲", "Die"],
- // black and white icons in FF:
- ["26A0", "⚠", "Alert"],
- ["2317", "⌗", "Hash"],
- ["2318", "⌘", "POI"],
- ["2307", "⌇", "Wavy"],
- ["27F1", "⟱", "Downwards Quadruple"],
- ["21E6", "⇦", "Left arrow"],
- ["21E7", "⇧", "Top arrow"],
- ["21E8", "⇨", "Right arrow"],
- ["21E9", "⇩", "Left arrow"],
- ["21F6", "⇶", "Three arrows"],
- ["2699", "⚙", "Gear"],
- ["269B", "⚛", "Atom"],
- ["2680", "⚀", "Die1"],
- ["2681", "⚁", "Die2"],
- ["2682", "⚂", "Die3"],
- ["2683", "⚃", "Die4"],
- ["2684", "⚄", "Die5"],
- ["2685", "⚅", "Die6"],
- ["26B4", "⚴", "Pallas"],
- ["26B5", "⚵", "Juno"],
- ["26B6", "⚶", "Vesta"],
- ["26B7", "⚷", "Chiron"],
- ["26B8", "⚸", "Lilith"],
- ["263F", "☿", "Mercury"],
- ["2640", "♀", "Venus"],
- ["2641", "♁", "Earth"],
- ["2642", "♂", "Mars"],
- ["2643", "♃", "Jupiter"],
- ["2644", "♄", "Saturn"],
- ["2645", "♅", "Uranus"],
- ["2646", "♆", "Neptune"],
- ["2647", "♇", "Pluto"],
- ["26B3", "⚳", "Ceres"],
- ["2654", "♔", "Chess king"],
- ["2655", "♕", "Chess queen"],
- ["2656", "♖", "Chess rook"],
- ["2657", "♗", "Chess bishop"],
- ["2658", "♘", "Chess knight"],
- ["2659", "♙", "Chess pawn"],
- ["2660", "♠", "Spade"],
- ["2663", "♣", "Club"],
- ["2665", "♥", "Heart"],
- ["2666", "♦", "Diamond"],
- ["2698", "⚘", "Flower"],
- ["2625", "☥", "Ankh"],
- ["2626", "☦", "Orthodox"],
- ["2627", "☧", "Chi Rho"],
- ["2628", "☨", "Lorraine"],
- ["2629", "☩", "Jerusalem"],
- ["2670", "♰", "Syriac cross"],
- ["2020", "†", "Dagger"],
- ["262A", "☪", "Muslim"],
- ["262D", "☭", "Soviet"],
- ["262E", "☮", "Peace"],
- ["262F", "☯", "Yin yang"],
- ["26A4", "⚤", "Heterosexuality"],
- ["26A2", "⚢", "Female homosexuality"],
- ["26A3", "⚣", "Male homosexuality"],
- ["26A5", "⚥", "Male and female"],
- ["26AD", "⚭", "Rings"],
- ["2690", "⚐", "White flag"],
- ["2691", "⚑", "Black flag"],
- ["263C", "☼", "Sun"],
- ["263E", "☾", "Moon"],
- ["2668", "♨", "Hot springs"],
- ["2600", "☀", "Black sun"],
- ["2601", "☁", "Cloud"],
- ["2602", "☂", "Umbrella"],
- ["2603", "☃", "Snowman"],
- ["2604", "☄", "Comet"],
- ["2605", "★", "Black star"],
- ["2606", "☆", "White star"],
- ["269D", "⚝", "Outlined star"],
- ["2618", "☘", "Shamrock"],
- ["21AF", "↯", "Lightning"],
- ["269C", "⚜", "FleurDeLis"],
- ["2622", "☢", "Radiation"],
- ["2623", "☣", "Biohazard"],
- ["2620", "☠", "Skull"],
- ["2638", "☸", "Dharma"],
- ["2624", "☤", "Caduceus"],
- ["2695", "⚕", "Aeculapius staff"],
- ["269A", "⚚", "Hermes staff"],
- ["2697", "⚗", "Alembic"],
- ["266B", "♫", "Music"],
- ["2702", "✂", "Scissors"],
- ["2696", "⚖", "Scales"],
- ["2692", "⚒", "Hammer and pick"],
- ["2694", "⚔", "Swords"]
- ];
-
- const table = document.getElementById("markerIconTable");
- table.addEventListener("click", selectIcon, false);
- table.addEventListener("mouseover", hoverIcon, false);
- let row = "";
-
- for (let i=0; i < icons.length; i++) {
- if (i%16 === 0) row = table.insertRow(0);
- const cell = row.insertCell(0);
- const icon = String.fromCodePoint(parseInt(icons[i][0], 16));
- cell.innerHTML = icon;
- cell.id = "markerIcon" + icon.codePointAt();
- cell.dataset.desc = icons[i][2];
- }
- }
-
- function selectIcon(e) {
- if (e.target !== e.currentTarget) {
- const table = document.getElementById("markerIconTable");
- const selected = table.getElementsByClassName("selected");
- if (selected.length) selected[0].removeAttribute("class");
- e.target.className = "selected";
+ function selectMarkerIcon() {
+ selectIcon(this.innerHTML, v => {
+ this.innerHTML = v;
const id = elSelected.attr("data-id");
- const icon = e.target.innerHTML;
- d3.select("#defs-markers").select(id).select("text").text(icon);
- }
- e.stopPropagation();
- }
-
- function hoverIcon(e) {
- if (e.target !== e.currentTarget) tip(e.target.innerHTML + " " + e.target.dataset.desc);
- e.stopPropagation();
+ d3.select("#defs-markers").select(id).select("text").text(v);
+ });
}
function changeIconSize() {
diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js
index 7a0dd5e4..b720486f 100644
--- a/modules/ui/military-overview.js
+++ b/modules/ui/military-overview.js
@@ -183,7 +183,7 @@ function overviewMilitary() {
position: {my: "center", at: "center", of: "svg"},
buttons: {
Apply: applyMilitaryOptions,
- Add: () => addUnitLine({name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, type: "melee"}),
+ Add: () => addUnitLine({icon: "🛡️", name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, power: 1, type: "melee"}),
Restore: restoreDefaultUnits,
Cancel: function() {$(this).dialog("close");}
}, open: function() {
@@ -200,19 +200,20 @@ function overviewMilitary() {
}
function addUnitLine(u) {
- const row = `
+ const row = document.createElement("tr");
+ row.innerHTML = ` |
|
|
|
- |
+ |
+ |
|
-
- |
- |
-
`;
- table.insertAdjacentHTML("beforeend", row);
+
+
| `;
+ row.querySelector("button").addEventListener("click", function(e) {selectIcon(this.innerHTML, v => this.innerHTML = v)});
+ table.appendChild(row);
}
function restoreDefaultUnits() {
@@ -230,8 +231,14 @@ function overviewMilitary() {
$("#militaryOptions").dialog("close");
options.military = unitLines.map((r, i) => {
- const [name, rural, urban, crew, type, separate] = Array.from(r.querySelectorAll("input, select")).map(d => d.type === "checkbox" ? d.checked : d.value);
- return {name:names[i], rural:+rural||0, urban:+urban||0, crew:+crew||0, type, separate:+separate||0};
+ 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;
+ });
+ return {icon, name:names[i], rural, urban, crew, power, type, separate};
});
localStorage.setItem("military", JSON.stringify(options.military));
Military.generate();
diff --git a/modules/ui/regiment-editor.js b/modules/ui/regiment-editor.js
index 4eaac873..d4a4f148 100644
--- a/modules/ui/regiment-editor.js
+++ b/modules/ui/regiment-editor.js
@@ -14,8 +14,7 @@ function editRegiment(selector) {
$("#regimentEditor").dialog({
title: "Edit Regiment", resizable: false, close: closeEditor,
- position: {my: "left top", at: "left+10 top+10", of: "#map"},
- close: closeEditor
+ position: {my: "left top", at: "left+10 top+10", of: "#map"}
});
if (modules.editRegiment) return;
@@ -27,6 +26,7 @@ function editRegiment(selector) {
document.getElementById("regimentName").addEventListener("change", changeName);
document.getElementById("regimentEmblem").addEventListener("input", changeEmblem);
document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem);
+ document.getElementById("regimentAttack").addEventListener("click", toggleAttack);
document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
document.getElementById("regimentLegend").addEventListener("click", editLegend);
document.getElementById("regimentSplit").addEventListener("click", splitRegiment);
@@ -92,50 +92,15 @@ function editRegiment(selector) {
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
}
+ function selectEmblem() {
+ selectIcon(regimentEmblem.value, v => {regimentEmblem.value = v; changeEmblem()});
+ }
+
function changeEmblem() {
const emblem = document.getElementById("regimentEmblem").value;
regiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
}
- function selectEmblem() {
- const emblems = ["⚔️","🏹","🐴","💣","🌊","🎯","⚓","🔮","📯","🛡️","👑",
- "☠️","🎆","🗡️","⛏️","🔥","🐾","🎪","🏰","⚜️","⛓️","❤️","📜","🔱","🌈","🌠","💥","☀️","🍀",
- "🔰","🕸️","⚗️","☣️","☢️","🎖️","⚕️","☸️","✡️","🚩","🏳️","🏴","🌈","💪","✊","👊","🤜","🤝","🙏","🧙","💂","🤴","🧛","🧟","🧞","🧝",
- "🦄","🐲","🐉","🐎","🦓","🐺","🦊","🐱","🐈","🦁","🐯","🐅","🐆","🐕","🦌","🐵","🐒","🦍",
- "🦅","🕊️","🐓","🦇","🐦","🦉","🐮","🐄","🐂","🐃","🐷","🐖","🐗","🐏","🐑","🐐","🐫","🦒","🐘","🦏",
- "🐭","🐁","🐀","🐹","🐰","🐇","🦔","🐸","🐊","🐢","🦎","🐍","🐳","🐬","🦈","🐙","🦑","🐌","🦋","🐜","🐝","🐞","🦗","🕷️","🦂","🦀"];
-
- alertMessage.innerHTML = "";
- const container = document.createElement("div");
- container.style.userSelect = "none";
- container.style.cursor = "pointer";
- container.style.fontSize = "2em";
- container.style.width = "47vw";
- container.innerHTML = emblems.map(i => `
${i}`).join("");
- container.addEventListener("mouseover", e => showTip(e), false);
- container.addEventListener("click", e => clickEmblem(e), false);
- alertMessage.appendChild(container);
-
- $("#alert").dialog({
- resizable: false, width: fitContent(), title: "Select emblem",
- buttons: {
- Emojipedia: function() {openURL("https://emojipedia.org/");},
- Close: function() {$(this).dialog("close");}
- }
- });
-
- function showTip(e) {
- if (e.target.tagName !== "SPAN") return;
- tip(`Click to select ${e.target.innerHTML} emblem`);
- }
-
- function clickEmblem(e) {
- if (e.target.tagName !== "SPAN") return;
- document.getElementById("regimentEmblem").value = e.target.innerHTML;
- changeEmblem();
- }
- }
-
function changeUnit() {
const u = this.dataset.u;
const reg = regiment();
@@ -200,6 +165,60 @@ function editRegiment(selector) {
toggleAdd();
}
+ function toggleAttack() {
+ document.getElementById("regimentAttack").classList.toggle("pressed");
+ if (document.getElementById("regimentAttack").classList.contains("pressed")) {
+ viewbox.style("cursor", "crosshair").on("click", attackRegimentOnClick);
+ tip("Click on another regiment to initiate battle", true);
+ armies.selectAll(":scope > g").classed("draggable", false);
+ } else {
+ clearMainTip();
+ armies.selectAll(":scope > g").classed("draggable", true);
+ viewbox.on("click", clicked).style("cursor", "default");
+ }
+ }
+
+ function attackRegimentOnClick() {
+ const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement;
+ const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state;
+
+ if (army.parentElement.id !== "armies") {tip("Please click on a regiment to attack", false, "error"); return;}
+ if (regSelected === elSelected) {tip("Regiment cannot attack itself", false, "error"); return;}
+ if (oldState === newState) {tip("Cannot attack fraternal regiment", false, "error"); return;}
+
+ const attacker = regiment();
+ const defender = pack.states[regSelected.dataset.state].military.find(r => r.i == regSelected.dataset.id);
+ if (!attacker.a || !defender.a) {tip("Regiment has no troops to battle", false, "error"); return;}
+
+ // move attacked to defender
+ const duration = Math.hypot(attacker.x - defender.x, attacker.y - defender.y) * 6;
+ const x = attacker.x = defender.x;
+ const y = attacker.y = defender.y + 8;
+
+ const size = +armies.attr("box-size");
+ const w = attacker.n ? size * 4 : size * 6;
+ const h = size * 2;
+ const x1 = x => rn(x - w / 2, 2);
+ const y1 = y => rn(y - size, 2);
+
+ const move = d3.transition().duration(duration).ease(d3.easeSinInOut);
+ const attack = d3.transition().delay(duration).duration(800).ease(d3.easeSinInOut).on("end", () => showBattleScreen(attacker, defender));
+
+ d3.select(elSelected.querySelector("rect")).transition(move).attr("x", x1(x)).attr("y", y1(y));
+ d3.select(elSelected.querySelector("text")).transition(move).attr("x", x).attr("y", y);
+ d3.select(elSelected.querySelectorAll("rect")[1]).transition(move).attr("x", x1(x)-h).attr("y", y1(y));
+ d3.select(elSelected.querySelector(".regimentIcon")).transition(move).attr("x", x1(x)-size).attr("y", y);
+
+ // battle icon
+ svg.append("text").attr("x", window.innerWidth/2).attr("y", window.innerHeight/2)
+ .text("⚔️").attr("font-size", 0).attr("opacity", 1)
+ .style("dominant-baseline", "central").style("text-anchor", "middle")
+ .transition(attack).attr("font-size", 1000).attr("opacity", 0).remove();
+
+ tip("", false);
+ $("#regimentEditor").dialog("close");
+ }
+
function toggleAttach() {
document.getElementById("regimentAttach").classList.toggle("pressed");
if (document.getElementById("regimentAttach").classList.contains("pressed")) {
@@ -289,7 +308,7 @@ function editRegiment(selector) {
const x1 = x => rn(x - w / 2, 2);
const y1 = y => rn(y - size, 2);
- const baseRect = this.querySelectorAll("rect")[0];
+ const baseRect = this.querySelector("rect");
const text = this.querySelector("text");
const iconRect = this.querySelectorAll("rect")[1];
const icon = this.querySelector(".regimentIcon");
@@ -330,6 +349,7 @@ function editRegiment(selector) {
armies.selectAll("g>g").call(d3.drag().on("drag", null));
viewbox.selectAll("g#regimentBase").remove();
document.getElementById("regimentAdd").classList.remove("pressed");
+ document.getElementById("regimentAttack").classList.remove("pressed");
document.getElementById("regimentAttach").classList.remove("pressed");
restoreDefaultEvents();
elSelected = null;
diff --git a/modules/ui/style.js b/modules/ui/style.js
index 88f2f16d..6550c541 100644
--- a/modules/ui/style.js
+++ b/modules/ui/style.js
@@ -52,13 +52,13 @@ function selectStyleElement() {
if (sel == "ocean") el = oceanLayers.select("rect");
// fill
- if (sel === "rivers" || sel === "lakes" || sel === "landmass" || sel === "prec" || sel === "fogging") {
+ if (sel === "rivers" || sel === "lakes" || sel === "landmass" || sel === "prec" || sel === "ice" || sel === "fogging") {
styleFill.style.display = "block";
styleFillInput.value = styleFillOutput.value = el.attr("fill");
}
// stroke color and width
- if (sel === "armies" ||sel === "routes" || sel === "lakes" || sel === "borders" || sel === "cults" || sel === "cells" || sel === "gridOverlay" || sel === "coastline" || sel === "prec" || sel === "icons" || sel === "coordinates"|| sel === "zones") {
+ if (sel === "armies" ||sel === "routes" || sel === "lakes" || sel === "borders" || sel === "cults" || sel === "cells" || sel === "gridOverlay" || sel === "coastline" || sel === "prec" || sel === "ice" || sel === "icons" || sel === "coordinates"|| sel === "zones") {
styleStroke.style.display = "block";
styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke");
styleStrokeWidth.style.display = "block";
@@ -623,8 +623,8 @@ styleArmiesSize.addEventListener("input", function() {
// request a URL to image to be used as a texture
function textureProvideURL() {
alertMessage.innerHTML = `Provide an image URL to be used as a texture:
-
-
`;
+
+
`;
$("#alert").dialog({resizable: false, title: "Load custom texture", width: "26em",
buttons: {
Apply: function() {
@@ -662,7 +662,7 @@ function fetchTextureURL(url) {
console.log("Provided URL is", url);
const img = new Image();
img.onload = function () {
- const canvas = document.getElementById("preview");
+ const canvas = document.getElementById("texturePreview");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
@@ -697,22 +697,22 @@ function applyStyleOnLoad() {
function addDefaulsStyles() {
if (!localStorage.getItem("styleClean")) {
- const clean = `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
+ const clean = `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":1,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
localStorage.setItem("styleClean", clean);
}
if (!localStorage.getItem("styleGloom")) {
- const gloom = `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.5,"filter":""},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern3)"},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
+ const gloom = `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.5,"filter":""},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":1,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern3)"},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
localStorage.setItem("styleGloom", gloom);
}
if (!localStorage.getItem("styleAncient")) {
- const ancient = `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern1)"},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
+ const ancient = `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":1,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern1)"},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
localStorage.setItem("styleAncient", ancient);
}
if (!localStorage.getItem("styleMonochrome")) {
- const monochrome = `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":0},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
+ const monochrome = `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":1,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":0},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging-cont":{"opacity":null,"fill":null,"stroke-width":null}}`;
localStorage.setItem("styleMonochrome", monochrome);
}
}
@@ -722,6 +722,7 @@ function applyDefaultStyle() {
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", .3);
biomes.attr("opacity", null).attr("filter", null).attr("mask", null);
+ ice.attr("opacity", 1).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)");
stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null);
provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .2).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt").attr("filter", null);
cells.attr("opacity", null).attr("stroke", "#808080").attr("stroke-width", .1).attr("filter", null).attr("mask", null);
@@ -928,6 +929,7 @@ function addStylePreset() {
"#statesHalo":["opacity", "data-width", "stroke-width"],
"#provs":["opacity", "filter"],
"#temperature":["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
+ "#ice":["opacity", "fill", "stroke", "stroke-width", "filter"],
"#texture":["opacity", "filter", "mask"],
"#textureImage":["x", "y"],
"#zones":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index a7ac34f6..20b64977 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -65,6 +65,7 @@ function processFeatureRegeneration(event, button) {
if (button === "regenerateProvinces") regenerateProvinces(); else
if (button === "regenerateReligions") regenerateReligions(); else
if (button === "regenerateMilitary") regenerateMilitary(); else
+ if (button === "regenerateIce") regenerateIce(); else
if (button === "regenerateMarkers") regenerateMarkers(event); else
if (button === "regenerateZones") regenerateZones(event);
}
@@ -86,7 +87,7 @@ function recalculatePopulation() {
if (!b.i || b.removed) return;
const i = b.cell;
- b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i]) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
+ b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
if (b.capital) b.population = b.population * 1.3; // increase capital population
if (b.port) b.population = b.population * 1.3; // increase port population
b.population = rn(b.population * gauss(2,3,.6,20,3), 3);
@@ -249,6 +250,12 @@ function regenerateMilitary() {
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
}
+function regenerateIce() {
+ if (!layerIsOn("toggleIce")) toggleIce();
+ ice.selectAll("*").remove();
+ drawIce();
+}
+
function regenerateMarkers(event) {
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfMarkers(v));
else addNumberOfMarkers(gauss(1, .5, .3, 5, 2));
diff --git a/modules/utils.js b/modules/utils.js
index 89acad24..afcdd5ea 100644
--- a/modules/utils.js
+++ b/modules/utils.js
@@ -370,11 +370,16 @@ function biased(min, max, ex) {
}
// return array of values common for both array a and array b
-function intersect(a, b) {
+function common(a, b) {
const setB = new Set(b);
return [...new Set(a)].filter(a => setB.has(a));
}
+// clip polygon by graph bbox
+function clipPoly(points, secure = false) {
+ return polygonclip(points, [0, 0, graphWidth, graphHeight], secure);
+}
+
// check if char is vowel
function vowel(c) {
return "aeiouy".includes(c);
@@ -610,5 +615,5 @@ void function() {
cancel.addEventListener("click", () => prompt.style.display = "none");
}()
-// localStorageDB
-!function(){function e(t,o){return n?void(n.transaction("s").objectStore("s").get(t).onsuccess=function(e){var t=e.target.result&&e.target.result.v||null;o(t)}):void setTimeout(function(){e(t,o)},100)}var t=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;if(!t)return void console.error("indexDB not supported");var n,o={k:"",v:""},r=t.open("d2",1);r.onsuccess=function(e){n=this.result},r.onerror=function(e){console.error("indexedDB request error"),console.log(e)},r.onupgradeneeded=function(e){n=null;var t=e.target.result.createObjectStore("s",{keyPath:"k"});t.transaction.oncomplete=function(e){n=e.target.db}},window.ldb={get:e,set:function(e,t){o.k=e,o.v=t,n.transaction("s","readwrite").objectStore("s").put(o)}}}();
\ No newline at end of file
+// indexedDB; ldb object
+!function(){function e(t,o){return n?void(n.transaction("s").objectStore("s").get(t).onsuccess=function(e){var t=e.target.result&&e.target.result.v||null;o(t)}):void setTimeout(function(){e(t,o)},100)}var t=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;if(!t)return void console.error("indexedDB not supported");var n,o={k:"",v:""},r=t.open("d2",1);r.onsuccess=function(e){n=this.result},r.onerror=function(e){console.error("indexedDB request error"),console.log(e)},r.onupgradeneeded=function(e){n=null;var t=e.target.result.createObjectStore("s",{keyPath:"k"});t.transaction.oncomplete=function(e){n=e.target.db}},window.ldb={get:e,set:function(e,t){o.k=e,o.v=t,n.transaction("s","readwrite").objectStore("s").put(o)}}}();
\ No newline at end of file
diff --git a/run_php_server.bat b/run_php_server.bat
new file mode 100644
index 00000000..fe11833e
--- /dev/null
+++ b/run_php_server.bat
@@ -0,0 +1,3 @@
+start chrome.exe http://localhost:8000/
+@echo off
+php -S localhost:8000
\ No newline at end of file
diff --git a/upload.php b/upload.php
new file mode 100644
index 00000000..7cf942bc
--- /dev/null
+++ b/upload.php
@@ -0,0 +1,6 @@
+
\ No newline at end of file