Fantasy-Map-Generator/modules/ui/tools.js
2019-06-05 20:23:06 +03:00

287 lines
No EOL
11 KiB
JavaScript

// module to control the Tools options (click to edit, to re-geenerate, tp add)
"use strict";
toolsContent.addEventListener("click", function(event) {
if (customization) {tip("Please exit the customization mode first", false, "warning"); return;}
if (event.target.tagName !== "BUTTON") return;
const button = event.target.id;
// Click to open Editor buttons
if (button === "editHeightmapButton") editHeightmap(); else
if (button === "editBiomesButton") editBiomes(); else
if (button === "editStatesButton") editStates(); else
if (button === "editCulturesButton") editCultures(); else
if (button === "editNamesBaseButton") editNamesbase(); else
if (button === "editBurgsButton") editBurgs(); else
if (button === "editUnitsButton") editUnits();
// Click to Regenerate buttons
if (button === "regenerateStateLabels") {BurgsAndStates.drawStateLabels(); if (!layerIsOn("toggleLabels")) toggleLabels();} else
if (button === "regenerateReliefIcons") {ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief();} else
if (button === "regenerateRoutes") {Routes.regenerate(); if (!layerIsOn("toggleRoutes")) toggleRoutes();} else
if (button === "regenerateRivers") regenerateRivers(); else
if (button === "regeneratePopulation") recalculatePopulation(); else
if (button === "regenerateBurgs") regenerateBurgs(); else
if (button === "regenerateStates") regenerateStates();
// Click to Add buttons
if (button === "addLabel") toggleAddLabel(); else
if (button === "addBurgTool") toggleAddBurg(); else
if (button === "addRiver") toggleAddRiver(); else
if (button === "addRoute") toggleAddRoute(); else
if (button === "addMarker") toggleAddMarker();
});
function regenerateRivers() {
const heights = new Uint8Array(pack.cells.h);
Rivers.generate();
pack.cells.h = new Uint8Array(heights);
if (!layerIsOn("toggleRivers")) toggleRivers();
}
function recalculatePopulation() {
rankCells();
pack.burgs.forEach(b => {
if (!b.i || b.removed) return;
const i = b.cell;
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i]) / 3 + 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) b.population = rn(b.population * 1.3, 3); // increase port population
});
}
function regenerateBurgs() {
const cells = pack.cells, states = pack.states;
rankCells();
cells.burg = new Uint16Array(cells.i.length);
const burgs = pack.burgs = [0]; // clear burgs array
states.filter(s => s.i).forEach(s => s.capital = 0); // clear capitals
const burgsTree = d3.quadtree();
const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 10 / densityInput.value ** .8) + states.length : +manorsInput.value + states.length;
const spacing = (graphWidth + graphHeight) * 9 / burgsCount; // base min distance between towns
for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) {
const id = burgs.length;
const cell = sorted[i];
const x = cells.p[cell][0], y = cells.p[cell][1];
const s = spacing * Math.random() + 0.5; // randomize to make the placement not uniform
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
const state = cells.state[cell];
const capital = !states[state].capital; // if state doesn't have capital, make this burg a capital
if (capital) {states[state].capital = id; states[state].cell = cell;}
const culture = cells.culture[cell];
const name = Names.getCulture(culture);
burgs.push({cell, x, y, state, i: id, culture, name, capital, feature: cells.f[cell]});
burgsTree.add([x, y]);
cells.burg[cell] = id;
}
// add a capital at former place for states without added capitals
states.filter(s => s.i && !s.removed && !s.capital).forEach(s => {
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
s.capital = burg;
pack.burgs[burg].capital = true;
pack.burgs[burg].state = s.i;
moveBurgToGroup(burg, "cities");
});
BurgsAndStates.specifyBurgs();
BurgsAndStates.drawBurgs();
Routes.regenerate();
}
function regenerateStates() {
}
function unpressClickToAddButton() {
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
restoreDefaultEvents();
clearMainTip();
}
function toggleAddLabel() {
const pressed = document.getElementById("addLabel").classList.contains("pressed");
if (pressed) {unpressClickToAddButton(); return;}
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
addLabel.classList.add('pressed');
closeDialogs(".stable");
viewbox.style("cursor", "crosshair").on("click", addLabelOnClick);
tip("Click on map to place label. Hold Shift to add multiple", true);
if (!layerIsOn("toggleLabels")) toggleLabels();
}
function addLabelOnClick() {
const point = d3.mouse(this);
// get culture in clicked point to generate a name
const cell = findCell(point[0], point[1]);
const culture = pack.cells.culture[cell];
const name = Names.getCulture(culture);
const id = getNextId("label");
let group = labels.select("#addedLabels");
if (!group.size()) group = labels.append("g").attr("id", "addedLabels")
.attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a")
.attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC")
.attr("font-size", 18).attr("data-size", 18).attr("filter", null);
group.append("text").attr("id", id)
.append("textPath").attr("xlink:href", "#textPath_"+id).text(name)
.attr("startOffset", "50%").attr("font-size", "100%");
defs.select("#textPaths").append("path").attr("id", "textPath_"+id)
.attr("d", `M${point[0]-60},${point[1]} h${120}`);
if (d3.event.shiftKey === false) unpressClickToAddButton();
}
function toggleAddBurg() {
unpressClickToAddButton();
document.getElementById("addBurgTool").classList.add("pressed");
editBurgs();
document.getElementById("addNewBurg").click();
}
function toggleAddRiver() {
const pressed = document.getElementById("addRiver").classList.contains("pressed");
if (pressed) {unpressClickToAddButton(); return;}
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
addRiver.classList.add('pressed');
closeDialogs(".stable");
viewbox.style("cursor", "crosshair").on("click", addRiverOnClick);
tip("Click on map to place new river or extend an existing one. Hold Shift to place multiple rivers", true);
if (!layerIsOn("toggleRivers")) toggleRivers();
}
function addRiverOnClick() {
const cells = pack.cells;
const point = d3.mouse(this);
let i = findCell(point[0], point[1]);
if (cells.r[i] || cells.h[i] < 20 || cells.b[i]) return;
const dataRiver = []; // to store river points
const river = +getNextId("river").slice(5); // river id
cells.fl[i] = grid.cells.prec[cells.g[i]]; // initial flux
let render = true;
while (i) {
cells.r[i] = river;
const x = cells.p[i][0], y = cells.p[i][1];
dataRiver.push({x, y, cell:i});
const min = cells.c[i][d3.scan(cells.c[i], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell
if (cells.h[i] <= cells.h[min]) {
tip(`Clicked cell is depressed! To resolve edit the heightmap and allow system to change heights`, false, "error");
render = false;
break;
}
const tx = cells.p[min][0], ty = cells.p[min][1];
if (cells.h[min] < 20) {
const px = (x + tx) / 2;
const py = (y + ty) / 2;
dataRiver.push({x: px, y: py, cell:i});
break;
}
if (!cells.r[min]) {
cells.fl[min] += cells.fl[i];
i = min;
continue;
}
const r = cells.r[min];
const riverCellsUpper = cells.i.filter(i => cells.r[i] === r && cells.h[i] > cells.h[min]);
// new river is not perspective
if (dataRiver.length <= riverCellsUpper.length) {
cells.conf[min] += cells.fl[i];
dataRiver.push({x: tx, y: ty, cell: min});
break;
}
// new river is more perspective
rivers.select("#river"+r).remove();
riverCellsUpper.forEach(i => cells.r[i] = 0);
if (riverCellsUpper.length > 1) {
// redraw upper part of the old river
}
cells.conf[min] = cells.fl[min];
cells.fl[min] = cells.fl[i] + grid.cells.prec[cells.g[min]];
i = min;
}
if (!render) return;
const points = Rivers.addMeandring(dataRiver, Math.random() * .5 + .1);
const width = Math.random() * .5 + .9;
const increment = Math.random() * .4 + .8;
const d = Rivers.getPath(points, width, increment);
rivers.append("path").attr("d", d).attr("id", "river"+river).attr("data-width", width).attr("data-increment", increment);
if (d3.event.shiftKey === false) unpressClickToAddButton();
}
function toggleAddRoute() {
const pressed = document.getElementById("addRoute").classList.contains("pressed");
if (pressed) {unpressClickToAddButton(); return;}
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
addRoute.classList.add('pressed');
closeDialogs(".stable");
viewbox.style("cursor", "crosshair").on("click", addRouteOnClick);
tip("Click on map to add a first control point", true);
if (!layerIsOn("toggleRoutes")) toggleRoutes();
}
function addRouteOnClick() {
unpressClickToAddButton();
const point = d3.mouse(this);
const id = getNextId("route");
elSelected = routes.select("g").append("path").attr("id", id).attr("data-new", 1).attr("d", `M${point[0]},${point[1]}`);
editRoute(true);
}
function toggleAddMarker() {
const pressed = document.getElementById("addMarker").classList.contains("pressed");
if (pressed) {unpressClickToAddButton(); return;}
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
addMarker.classList.add('pressed');
closeDialogs(".stable");
viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick);
tip("Click on map to add a marker. Hold Shift to add multiple", true);
if (!layerIsOn("toggleMarkers")) toggleMarkers();
}
function addMarkerOnClick() {
const point = d3.mouse(this);
const x = rn(point[0], 2), y = rn(point[1], 2);
const id = getNextId("markerElement");
const selected = markerSelectGroup.value;
const valid = selected && d3.select("#defs-markers").select("#"+selected).size();
const symbol = valid ? "#"+selected : "#marker0";
const added = markers.select("[data-id='" + symbol + "']").size();
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr("data-size") : 1;
if (isNaN(desired)) desired = 1;
const size = desired * 5 + 25 / scale;
markers.append("use").attr("id", id).attr("xlink:href", symbol).attr("data-id", symbol)
.attr("data-x", x).attr("data-y", y).attr("x", x - size / 2).attr("y", y - size)
.attr("data-size", desired).attr("width", size).attr("height", size);
if (d3.event.shiftKey === false) unpressClickToAddButton();
}