diff --git a/index.css b/index.css
index 15891f96..fc66801c 100644
--- a/index.css
+++ b/index.css
@@ -524,12 +524,13 @@ p {
display: none;
text-align: center;
top: calc(98vh - (10px + 0.5vw));
- width: 100%;
- cursor: default;
- text-shadow: 1px 1px 2px #1d0e0f;
- color: #ffffff;
- font-size: calc(10px + 0.5vw);
- pointer-events: none;
+ width: 90%;
+ left: 5%;
+ cursor: default;
+ text-shadow: 1px 1px 3px #0e0e0e;
+ color: #ffffff;
+ font-size: calc(10px + 0.5vw);
+ pointer-events: none;
}
#optionsContent table td:nth-of-type(1) {
diff --git a/index.html b/index.html
index 4a4b07a1..23f5b13f 100644
--- a/index.html
+++ b/index.html
@@ -164,8 +164,8 @@
-
-
+
+
-
+
-
.map
-
.svg
-
.png
+
.map
+
.svg
+
.png
-
-
+
+
@@ -752,6 +752,7 @@
+
diff --git a/script.js b/script.js
index 7c1c408a..4a9303fc 100644
--- a/script.js
+++ b/script.js
@@ -37,7 +37,7 @@ function fantasyMap() {
labels = viewbox.append("g").attr("id", "labels"),
burgLabels = labels.append("g").attr("id", "burgLabels"),
icons = viewbox.append("g").attr("id", "icons"),
- burgIcons = icons.append("g").attr("id", "burgIcons")
+ burgIcons = icons.append("g").attr("id", "burgIcons"),
ruler = viewbox.append("g").attr("id", "ruler"),
debug = viewbox.append("g").attr("id", "debug");
@@ -1581,6 +1581,10 @@ function fantasyMap() {
const x = d3.event.x, y = d3.event.y;
const transform = `translate(${(dx+x)},${(dy+y)})`;
el.attr("transform", transform);
+ const pp = this.parentNode.parentNode.id;
+ if (pp === "burgIcons" || pp === "burgLabels") {
+ tip('Use dragging for fine-tuning only, to move burg to a different cell use "Relocate" button');
+ }
});
d3.event.on("end", function() {
@@ -2433,7 +2437,7 @@ function fantasyMap() {
function unselect() {
if (elSelected) {
- elSelected.call(d3.drag().on("drag", null)).classed("draggable", false);
+ elSelected.call(d3.drag().on("drag", null)).attr("class", null);
debug.select(".controlPoints").remove();
viewbox.style("cursor", "default");
elSelected = null;
@@ -2917,12 +2921,8 @@ function fantasyMap() {
}
function editBurg() {
- if (customization) {return;}
- if (elSelected) {
- const self = d3.select(this).attr("data-id") === elSelected.attr("data-id");
- if (self) {return;}
- }
-
+ if (customization) return;
+ unselect();
closeDialogs("#burgEditor, .stable");
elSelected = d3.select(this);
const id = +elSelected.attr("data-id");
@@ -2987,7 +2987,7 @@ function fantasyMap() {
});
}
- $("#burgEditor > button").not("#burgAddfromEditor").not("#burgRemove").click(function() {
+ $("#burgEditor > button").not("#burgAddfromEditor").not("#burgRelocate").not("#burgRemove").click(function() {
if ($(this).next().is(":visible")) {
$("#burgEditor > button").show();
$(this).next("div").hide();
@@ -3260,6 +3260,75 @@ function fantasyMap() {
manors[id].population = +this.value;
});
+ $("#burgRelocate").click(function() {
+ if ($(this).hasClass('pressed')) {
+ $(".pressed").removeClass('pressed');
+ restoreDefaultEvents();
+ tip("", true);
+ } else {
+ $(".pressed").removeClass('pressed');
+ const id = elSelected.attr("data-id");
+ $(this).addClass('pressed').attr("data-id", id);
+ viewbox.style("cursor", "crosshair").on("click", relocateBurgOnClick);
+ tip("Click on map to relocate burg. Hold Shift for continuous move", true);
+ }
+ });
+
+ // move burg to a different cell
+ function relocateBurgOnClick() {
+ const point = d3.mouse(this);
+ const index = getIndex(point);
+ const i = +$("#burgRelocate").attr("data-id");
+ if (isNaN(i) || !manors[i]) return;
+
+ if (cells[index].height < 0.2) {
+ tip("Cannot place burg in the water! Select a land cell", null, "error");
+ return;
+ }
+
+ if (cells[index].manor !== undefined && cells[index].manor !== i) {
+ tip("There is already a burg in this cell. Please select a free cell", null, "error");
+ $('#grid').fadeIn();
+ d3.select("#toggleGrid").classed("buttonoff", false);
+ return;
+ }
+
+ let region = cells[index].region;
+ const oldRegion = manors[i].region;
+ // relocating capital to other country you "conqure" thid cell
+ if (states[oldRegion] && states[oldRegion].capital === i) {
+ if (region !== oldRegion) {
+ tip("Capital cannot be moved to another country!", null, "error");
+ return;
+ }
+ }
+
+ if (d3.event.shiftKey === false) {
+ $("#burgRelocate").removeClass("pressed");
+ restoreDefaultEvents();
+ tip("", true);
+ if (region !== oldRegion) {
+ recalculateStateData(oldRegion);
+ recalculateStateData(region);
+ updateCountryEditors();
+ }
+ }
+
+ const x = rn(point[0], 2), y = rn(point[1], 2);
+ burgIcons.select("circle[data-id='"+i+"']").attr("cx", x).attr("cy", y);
+ burgLabels.select("text[data-id='"+i+"']").attr("x", x).attr("y", y);
+ const anchor = icons.select("use[data-id='"+i+"']");
+ if (anchor.size()) {
+ const size = anchor.attr("width");
+ const xa = rn(x - size * 0.47, 2);
+ const ya = rn(y - size * 0.47, 2);
+ icons.select("use[data-id='"+i+"']").attr("x", xa).attr("y", ya);
+ }
+ cells[index].manor = i;
+ cells[manors[i].cell].manor = undefined;
+ manors[i].x = x, manors[i].y = y, manors[i].region = region, manors[i].cell = index;
+ }
+
$("#burgAddfromEditor").click(function() {
clickToAdd(); // to load on click event function
$("#addBurg").click();
@@ -3393,7 +3462,8 @@ function fantasyMap() {
if (m.i === m.region && !cell.port) {score *= 2;} // land-capitals
if (m.i === m.region && cell.port) {score *= 3;} // port-capitals
if (m.region === "neutral") score *= urbanFactor;
- m.population = rn(score, 1);
+ const rnd = 0.6 + Math.random() * 0.8; // random factor
+ m.population = rn(score * rnd, 1);
});
// calculate rural population for each cell based on area + elevation (elevation to be changed to biome)
@@ -4296,7 +4366,10 @@ function fantasyMap() {
if (Math.random() < 0.85 || culture === null) {
// culture is random if capital is not yet defined
if (culture === null) culture = rand(cultures.length - 1);
- while (name.length > 8) {name = generateName(culture);}
+ // try to avoid too long words as a basename
+ for (let i=0; i < 20 && name.length > 7; i++) {
+ name = generateName(culture);
+ }
} else {
name = manors[state].name;
}
@@ -4309,10 +4382,30 @@ function fantasyMap() {
// remove -sk and -ev/-ov for Ruthenian
name = name.slice(0,-2);
addSuffix = true;
+ } else if (name.length > 5 && base === 1 && name.slice(-3) === "ton") {
+ // remove -ton ending for English
+ name = name.slice(0,-3);
+ addSuffix = true;
} else if (name.length > 6 && name.slice(-4) === "berg") {
// remove -berg ending for any
name = name.slice(0,-4);
addSuffix = true;
+ } else if (base === 12) {
+ // Japanese ends on vowels
+ if (vowels.includes(name.slice(-1))) return name;
+ return name + "u";
+ } else if (base === 10) {
+ // Korean has "guk" suffix
+ if (name.slice(-3) === "guk") return name;
+ if (name.slice(-1) === "g") name = name.slice(0,-1);
+ if (Math.random() < 0.2 && name.length < 7) name = name + "guk"; // 20% for "guk"
+ return name;
+ } else if (base === 11) {
+ // Chinese has "guo" suffix
+ if (name.slice(-3) === "guo") return name;
+ if (name.slice(-1) === "g") name = name.slice(0,-1);
+ if (Math.random() < 0.3 && name.length < 7) name = name + " Guo"; // 30% for "guo"
+ return name;
}
// define if suffix should be used
@@ -4332,7 +4425,6 @@ function fantasyMap() {
}
if (addSuffix === false) return name;
-
let suffix = "ia"; // common latin suffix
const rnd = Math.random();
if (rnd < 0.05 && base === 3) suffix = "terra"; // 5% "terra" for Italian
@@ -4341,6 +4433,8 @@ function fantasyMap() {
else if (rnd < 0.5 && base == 0) suffix = "land"; // 50% "land" for German
else if (rnd < 0.4 && base == 1) suffix = "land"; // 40% "land" for English
else if (rnd < 0.3 && base == 6) suffix = "land"; // 30% "land" for Nordic
+ else if (rnd < 0.1 && base == 7) suffix = "eia"; // 10% "eia" for Greek ("ia" is also Greek)
+ else if (rnd < 0.4 && base == 9) suffix = "maa"; // 40% "maa" for Finnic
if (name.slice(-1 * suffix.length) === suffix) return name; // no suffix if name already ends with it
return name + suffix;
}
@@ -4756,7 +4850,7 @@ function fantasyMap() {
// Add support "click to add" button events
$("#customizeTab").click(function() {clickToAdd()});
function clickToAdd() {
- if (modules.clickToAdd) {return;}
+ if (modules.clickToAdd) return;
modules.clickToAdd = true;
// add label on click
@@ -4802,11 +4896,11 @@ function fantasyMap() {
if ($(this).hasClass('pressed')) {
$(".pressed").removeClass('pressed');
restoreDefaultEvents();
+ tip("", true);
} else {
$(".pressed").removeClass('pressed');
$(this).attr("data-state", -1).addClass('pressed');
- $("#burgAdd").addClass('pressed');
- closeDialogs(".stable");
+ $("#burgAdd, #burgAddfromEditor").addClass('pressed');
viewbox.style("cursor", "crosshair").on("click", addBurgOnClick);
tip("Click on map to place burg icon with a label. Hold Shift to place several", true);
}
@@ -4823,11 +4917,11 @@ function fantasyMap() {
const name = generateName(culture);
if (cells[index].height < 0.2) {
- tip("Cannot place burg in the water! Select a land cell");
+ tip("Cannot place burg in the water! Select a land cell", null, "error");
return;
}
if (cells[index].manor !== undefined) {
- tip("There is already a burg in this cell. You have to select a free cell");
+ tip("There is already a burg in this cell. Please select a free cell", null, "error");
$('#grid').fadeIn();
d3.select("#toggleGrid").classed("buttonoff", false);
return;
@@ -4839,7 +4933,7 @@ function fantasyMap() {
invokeActiveZooming();
if (d3.event.shiftKey === false) {
- $("#addBurg, #burgAdd").removeClass("pressed");
+ $("#addBurg, #burgAdd, #burgAddfromEditor").removeClass("pressed");
restoreDefaultEvents();
}
@@ -5011,7 +5105,7 @@ function fantasyMap() {
// re-calculate data for a particular state
function recalculateStateData(state) {
- const s = states[state];
+ const s = states[state] || states[states.length - 1];
if (s.capital === "neutral") state = "neutral";
const burgs = $.grep(manors, function(e) {return e.region === state;});
s.burgs = burgs.length;
@@ -5717,7 +5811,7 @@ function fantasyMap() {
const extent = (100 / scaleTo) + "%";
const xShift = (nWidth * scaleTo - svgWidth) / 2 / scaleTo;
const yShift = (nHeight * scaleTo - svgHeight) / 2 / scaleTo;
- ocean.selectAll("rect").attr("x", xShift).attr("y", yShift).attr("width", extent).attr("height", extent);
+ svg.select("#ocean").selectAll("rect").attr("x", xShift).attr("y", yShift).attr("width", extent).attr("height", extent);
zoom.translateExtent([[0, 0], [nWidth, nHeight]]).scaleExtent([scaleTo, 20]).scaleTo(svg, scaleTo);
$(this).dialog("close");
},
@@ -5767,8 +5861,8 @@ function fantasyMap() {
labels.append("g").attr("id", "burgLabels");
$("#labels #capitals, #labels #towns").detach().appendTo($("#burgLabels"));
labels.select("#burgLabels").selectAll("text").each(function() {
- let id = this.getAttribute("id");
- if (!id) return;
+ const id = this.getAttribute("id");
+ if (id === null || id === undefined) return;
this.removeAttribute("id");
this.setAttribute("data-id", +id.replace("manorLabel", ""));
});
@@ -5781,9 +5875,9 @@ function fantasyMap() {
icons.select("#burgIcons").select("#towns").attr("size", .5).attr("fill-opacity", .7).attr("stroke-opacity", 1);
}
- icons.selectAll("g").each(function(d) {
+ icons.selectAll("g").each(function() {
const size = this.getAttribute("font-size");
- if (size === undefined) return;
+ if (size === null || size === undefined) return;
this.removeAttribute("font-size");
this.setAttribute("size", size);
});
@@ -5791,13 +5885,14 @@ function fantasyMap() {
icons.select("#burgIcons").selectAll("circle").each(function() {
this.setAttribute("r", this.parentNode.getAttribute("size"));
const id = this.getAttribute("id");
- if (!id) return;
+ if (id === null || id === undefined) return;
this.removeAttribute("id");
this.setAttribute("data-id", +id.replace("manorIcon", ""));
});
icons.selectAll("use").each(function() {
const size = this.parentNode.getAttribute("size");
+ if (size === null || size === undefined) return;
this.setAttribute("width", size);
this.setAttribute("height", size);
});
@@ -5962,71 +6057,56 @@ function fantasyMap() {
}
}
- // Hotkeys
+ // Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
d3.select("body").on("keydown", function() {
const active = document.activeElement.tagName;
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return;
- switch(d3.event.keyCode) {
- case 27: // Escape to close all dialogs
- closeDialogs();
- break;
- case 79: // "O" to toggle options
- optionsTrigger.click();
- break;
- case 113: // "F2" for new map
- $("#randomMap").click();
- break;
- case 32: // Space to log focused cell data
- var point = d3.mouse(this);
- const index = diagram.find(point[0], point[1]).index;
- console.table(cells[index]);
- break;
- case 67: // "C" to log cells data
- console.log(cells);
- break;
- case 66: // "B" to log burgs data
- console.table(manors);
- break;
- case 83: // "S" to log states data
- console.table(states);
- break;
- case 70: // "F" to log features data
- console.table(features);
- break;
- case 37: // Left to scroll map left
- zoom.translateBy(svg, 10, 0);
- break;
- case 39: // Right to scroll map right
- zoom.translateBy(svg, -10, 0);
- break;
- case 38: // Up to scroll map up
- zoom.translateBy(svg, 0, 10);
- break;
- case 40: // Down to scroll map down
- zoom.translateBy(svg, 0, -10);
- break;
- case 107: // Plus to zoom map up
- zoom.scaleBy(svg, 1.2);
- break;
- case 109: // Minus to zoom map out
- zoom.scaleBy(svg, 0.8);
- break;
- case 9: // Tab to toggle full-screen mode
- $("#updateFullscreen").click();
- break;
- case 90: // Ctrl + "Z" to toggle undo
- if (customization !== 1) return;
- if (d3.event.ctrlKey === false) return;
- undo.click();
- break;
- case 89: // Ctrl + "Y" to toggle undo
- if (customization !== 1) return;
- if (d3.event.ctrlKey === false) return;
- redo.click();
- break;
- }
+ const key = d3.event.keyCode;
+ const ctrl = d3.event.ctrlKey;
+ const p = d3.mouse(this);
+ if (key === 117) $("#randomMap").click(); // "F6" for new map
+ else if (key === 27) closeDialogs(); // Escape to close all dialogs
+ else if (key === 79) optionsTrigger.click(); // "O" to toggle options
+ else if (key === 80) saveAsImage("png"); // "P" to save as PNG
+ else if (key === 83) saveAsImage("svg"); // "S" to save as SVG
+ else if (key === 77) saveMap(); // "M" to save MAP file
+ else if (key === 76) mapToLoad.click(); // "L" to load MAP
+ else if (key === 32) console.table(cells[diagram.find(p[0], p[1]).index]); // Space to log focused cell data
+ else if (key === 192) console.log(cells); // "`" to log cells data
+ else if (key === 66) console.table(manors); // "B" to log burgs data
+ else if (key === 67) console.table(states); // "C" to log countries data
+ else if (key === 70) console.table(features); // "F" to log features data
+ else if (key === 37) zoom.translateBy(svg, 10, 0); // Left to scroll map left
+ else if (key === 39) zoom.translateBy(svg, -10, 0); // Right to scroll map right
+ else if (key === 38) zoom.translateBy(svg, 0, 10); // Up to scroll map up
+ else if (key === 40) zoom.translateBy(svg, 0, -10); // Up to scroll map up
+ else if (key === 107) zoom.scaleBy(svg, 1.2); // Plus to zoom map up
+ else if (key === 109) zoom.scaleBy(svg, 0.8); // Minus to zoom map out
+ else if (key === 48 || key === 96) resetZoom(); // 0 to reset zoom
+ else if (key === 49 || key === 97) zoom.scaleTo(svg, 1); // 1 to zoom to 1
+ else if (key === 50 || key === 98) zoom.scaleTo(svg, 2); // 2 to zoom to 2
+ else if (key === 51 || key === 99) zoom.scaleTo(svg, 3); // 3 to zoom to 3
+ else if (key === 52 || key === 100) zoom.scaleTo(svg, 4); // 4 to zoom to 4
+ else if (key === 53 || key === 101) zoom.scaleTo(svg, 5); // 5 to zoom to 5
+ else if (key === 54 || key === 102) zoom.scaleTo(svg, 6); // 6 to zoom to 6
+ else if (key === 55 || key === 103) zoom.scaleTo(svg, 7); // 7 to zoom to 7
+ else if (key === 56 || key === 104) zoom.scaleTo(svg, 8); // 8 to zoom to 8
+ else if (key === 57 || key === 105) zoom.scaleTo(svg, 9); // 9 to zoom to 9
+ else if (key === 9) $("#updateFullscreen").click(); // Tab to fit map to fullscreen
+ else if (ctrl && key === 90) undo.click(); // Ctrl + "Z" to toggle undo
+ else if (ctrl && key === 89) redo.click(); // Ctrl + "Y" to toggle undo
});
+ // Show help
+ function showHelp() {
+ $("#help").dialog({
+ title: "About Fantasy Map Generator",
+ minHeight: 30, width: "auto", maxWidth: 275, resizable: false,
+ position: {my: "center top+10", at: "bottom", of: this},
+ close: unselect
+ });
+ }
+
// Toggle Options pane
$("#optionsTrigger").on("click", function() {
if (tooltip.getAttribute("data-main") === "Сlick the arrow button to open options") {
@@ -8557,7 +8637,8 @@ function fantasyMap() {
const width = Math.max(svgWidth, graphWidth);
const height = Math.max(svgHeight, graphHeight);
zoom.translateExtent([[0, 0], [width, height]]);
- ocean.selectAll("rect").attr("width", width).attr("height", height);
+ svg.select("#ocean").selectAll("rect").attr("x", 0)
+ .attr("y", 0).attr("width", width).attr("height", height);
}
// fit full-screen map if window is resized
@@ -8996,9 +9077,12 @@ function fantasyMap() {
}
}
-function tip(tip, main) {
- tooltip.innerHTML = tip;
- if (main) {tooltip.setAttribute("data-main", tip);}
+function tip(tip, main, error) {
+ const tooltip = d3.select("#tooltip");
+ const reg = "linear-gradient(0.1turn, #ffffff00, #5e5c5c33, #ffffff00)";
+ const red = "linear-gradient(0.1turn, #ffffff00, #c71d1d4d, #ffffff00)";
+ tooltip.text(tip).style("background", error ? red : reg);
+ if (main) tooltip.attr("data-main", tip);
}
$("#optionsContainer *").on("mouseout", function() {